summaryrefslogtreecommitdiff
path: root/src/com/android/phone/PhoneGlobals.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/phone/PhoneGlobals.java')
-rw-r--r--src/com/android/phone/PhoneGlobals.java1859
1 files changed, 1859 insertions, 0 deletions
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
new file mode 100644
index 00000000..019c74af
--- /dev/null
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -0,0 +1,1859 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import android.app.Activity;
+import android.app.KeyguardManager;
+import android.app.PendingIntent;
+import android.app.ProgressDialog;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.IBluetoothHeadsetPhone;
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.res.Configuration;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.AsyncResult;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IPowerManager;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UpdateLock;
+import android.os.UserHandle;
+import android.preference.PreferenceManager;
+import android.provider.Settings.System;
+import android.telephony.ServiceState;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Slog;
+import android.view.KeyEvent;
+
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallManager;
+import com.android.internal.telephony.IccCard;
+import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.telephony.MmiCode;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.TelephonyCapabilities;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.cdma.TtyIntent;
+import com.android.phone.common.CallLogAsync;
+import com.android.phone.OtaUtils.CdmaOtaScreenState;
+import com.android.server.sip.SipService;
+
+/**
+ * Global state for the telephony subsystem when running in the primary
+ * phone process.
+ */
+public class PhoneGlobals extends ContextWrapper
+ implements AccelerometerListener.OrientationListener {
+ /* package */ static final String LOG_TAG = "PhoneApp";
+
+ /**
+ * Phone app-wide debug level:
+ * 0 - no debug logging
+ * 1 - normal debug logging if ro.debuggable is set (which is true in
+ * "eng" and "userdebug" builds but not "user" builds)
+ * 2 - ultra-verbose debug logging
+ *
+ * Most individual classes in the phone app have a local DBG constant,
+ * typically set to
+ * (PhoneApp.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1)
+ * or else
+ * (PhoneApp.DBG_LEVEL >= 2)
+ * depending on the desired verbosity.
+ *
+ * ***** DO NOT SUBMIT WITH DBG_LEVEL > 0 *************
+ */
+ /* package */ static final int DBG_LEVEL = 0;
+
+ private static final boolean DBG =
+ (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
+ private static final boolean VDBG = (PhoneGlobals.DBG_LEVEL >= 2);
+
+ // Message codes; see mHandler below.
+ private static final int EVENT_SIM_NETWORK_LOCKED = 3;
+ private static final int EVENT_WIRED_HEADSET_PLUG = 7;
+ private static final int EVENT_SIM_STATE_CHANGED = 8;
+ private static final int EVENT_UPDATE_INCALL_NOTIFICATION = 9;
+ private static final int EVENT_DATA_ROAMING_DISCONNECTED = 10;
+ private static final int EVENT_DATA_ROAMING_OK = 11;
+ private static final int EVENT_UNSOL_CDMA_INFO_RECORD = 12;
+ private static final int EVENT_DOCK_STATE_CHANGED = 13;
+ private static final int EVENT_TTY_PREFERRED_MODE_CHANGED = 14;
+ private static final int EVENT_TTY_MODE_GET = 15;
+ private static final int EVENT_TTY_MODE_SET = 16;
+ private static final int EVENT_START_SIP_SERVICE = 17;
+
+ // The MMI codes are also used by the InCallScreen.
+ public static final int MMI_INITIATE = 51;
+ public static final int MMI_COMPLETE = 52;
+ public static final int MMI_CANCEL = 53;
+ // Don't use message codes larger than 99 here; those are reserved for
+ // the individual Activities of the Phone UI.
+
+ /**
+ * Allowable values for the wake lock code.
+ * SLEEP means the device can be put to sleep.
+ * PARTIAL means wake the processor, but we display can be kept off.
+ * FULL means wake both the processor and the display.
+ */
+ public enum WakeState {
+ SLEEP,
+ PARTIAL,
+ FULL
+ }
+
+ /**
+ * Intent Action used for hanging up the current call from Notification bar. This will
+ * choose first ringing call, first active call, or first background call (typically in
+ * HOLDING state).
+ */
+ public static final String ACTION_HANG_UP_ONGOING_CALL =
+ "com.android.phone.ACTION_HANG_UP_ONGOING_CALL";
+
+ /**
+ * Intent Action used for making a phone call from Notification bar.
+ * This is for missed call notifications.
+ */
+ public static final String ACTION_CALL_BACK_FROM_NOTIFICATION =
+ "com.android.phone.ACTION_CALL_BACK_FROM_NOTIFICATION";
+
+ /**
+ * Intent Action used for sending a SMS from notification bar.
+ * This is for missed call notifications.
+ */
+ public static final String ACTION_SEND_SMS_FROM_NOTIFICATION =
+ "com.android.phone.ACTION_SEND_SMS_FROM_NOTIFICATION";
+
+ private static PhoneGlobals sMe;
+
+ // A few important fields we expose to the rest of the package
+ // directly (rather than thru set/get methods) for efficiency.
+ Phone phone;
+ CallController callController;
+ InCallUiState inCallUiState;
+ CallerInfoCache callerInfoCache;
+ CallNotifier notifier;
+ NotificationMgr notificationMgr;
+ Ringer ringer;
+ IBluetoothHeadsetPhone mBluetoothPhone;
+ PhoneInterfaceManager phoneMgr;
+ CallManager mCM;
+ CallStateMonitor callStateMonitor;
+ int mBluetoothHeadsetState = BluetoothProfile.STATE_DISCONNECTED;
+ int mBluetoothHeadsetAudioState = BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
+ boolean mShowBluetoothIndication = false;
+ static int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
+ static boolean sVoiceCapable = true;
+
+ // Internal PhoneApp Call state tracker
+ CdmaPhoneCallState cdmaPhoneCallState;
+
+ // The InCallScreen instance (or null if the InCallScreen hasn't been
+ // created yet.)
+ private InCallScreen mInCallScreen;
+
+ // The currently-active PUK entry activity and progress dialog.
+ // Normally, these are the Emergency Dialer and the subsequent
+ // progress dialog. null if there is are no such objects in
+ // the foreground.
+ private Activity mPUKEntryActivity;
+ private ProgressDialog mPUKEntryProgressDialog;
+
+ private boolean mIsSimPinEnabled;
+ private String mCachedSimPin;
+
+ // True if a wired headset is currently plugged in, based on the state
+ // from the latest Intent.ACTION_HEADSET_PLUG broadcast we received in
+ // mReceiver.onReceive().
+ private boolean mIsHeadsetPlugged;
+
+ // True if the keyboard is currently *not* hidden
+ // Gets updated whenever there is a Configuration change
+ private boolean mIsHardKeyboardOpen;
+
+ // True if we are beginning a call, but the phone state has not changed yet
+ private boolean mBeginningCall;
+
+ // Last phone state seen by updatePhoneState()
+ private PhoneConstants.State mLastPhoneState = PhoneConstants.State.IDLE;
+
+ private WakeState mWakeState = WakeState.SLEEP;
+
+ private PowerManager mPowerManager;
+ private IPowerManager mPowerManagerService;
+ private PowerManager.WakeLock mWakeLock;
+ private PowerManager.WakeLock mPartialWakeLock;
+ private PowerManager.WakeLock mProximityWakeLock;
+ private KeyguardManager mKeyguardManager;
+ private AccelerometerListener mAccelerometerListener;
+ private int mOrientation = AccelerometerListener.ORIENTATION_UNKNOWN;
+
+ private UpdateLock mUpdateLock;
+
+ // Broadcast receiver for various intent broadcasts (see onCreate())
+ private final BroadcastReceiver mReceiver = new PhoneAppBroadcastReceiver();
+
+ // Broadcast receiver purely for ACTION_MEDIA_BUTTON broadcasts
+ private final BroadcastReceiver mMediaButtonReceiver = new MediaButtonBroadcastReceiver();
+
+ /** boolean indicating restoring mute state on InCallScreen.onResume() */
+ private boolean mShouldRestoreMuteOnInCallResume;
+
+ /**
+ * The singleton OtaUtils instance used for OTASP calls.
+ *
+ * The OtaUtils instance is created lazily the first time we need to
+ * make an OTASP call, regardless of whether it's an interactive or
+ * non-interactive OTASP call.
+ */
+ public OtaUtils otaUtils;
+
+ // Following are the CDMA OTA information Objects used during OTA Call.
+ // cdmaOtaProvisionData object store static OTA information that needs
+ // to be maintained even during Slider open/close scenarios.
+ // cdmaOtaConfigData object stores configuration info to control visiblity
+ // of each OTA Screens.
+ // cdmaOtaScreenState object store OTA Screen State information.
+ public OtaUtils.CdmaOtaProvisionData cdmaOtaProvisionData;
+ public OtaUtils.CdmaOtaConfigData cdmaOtaConfigData;
+ public OtaUtils.CdmaOtaScreenState cdmaOtaScreenState;
+ public OtaUtils.CdmaOtaInCallScreenUiState cdmaOtaInCallScreenUiState;
+
+ // TTY feature enabled on this platform
+ private boolean mTtyEnabled;
+ // Current TTY operating mode selected by user
+ private int mPreferredTtyMode = Phone.TTY_MODE_OFF;
+
+ /**
+ * Set the restore mute state flag. Used when we are setting the mute state
+ * OUTSIDE of user interaction {@link PhoneUtils#startNewCall(Phone)}
+ */
+ /*package*/void setRestoreMuteOnInCallResume (boolean mode) {
+ mShouldRestoreMuteOnInCallResume = mode;
+ }
+
+ /**
+ * Get the restore mute state flag.
+ * This is used by the InCallScreen {@link InCallScreen#onResume()} to figure
+ * out if we need to restore the mute state for the current active call.
+ */
+ /*package*/boolean getRestoreMuteOnInCallResume () {
+ return mShouldRestoreMuteOnInCallResume;
+ }
+
+ Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ PhoneConstants.State phoneState;
+ switch (msg.what) {
+ // Starts the SIP service. It's a no-op if SIP API is not supported
+ // on the deivce.
+ // TODO: Having the phone process host the SIP service is only
+ // temporary. Will move it to a persistent communication process
+ // later.
+ case EVENT_START_SIP_SERVICE:
+ SipService.start(getApplicationContext());
+ break;
+
+ // TODO: This event should be handled by the lock screen, just
+ // like the "SIM missing" and "Sim locked" cases (bug 1804111).
+ case EVENT_SIM_NETWORK_LOCKED:
+ if (getResources().getBoolean(R.bool.ignore_sim_network_locked_events)) {
+ // Some products don't have the concept of a "SIM network lock"
+ Log.i(LOG_TAG, "Ignoring EVENT_SIM_NETWORK_LOCKED event; "
+ + "not showing 'SIM network unlock' PIN entry screen");
+ } else {
+ // Normal case: show the "SIM network unlock" PIN entry screen.
+ // The user won't be able to do anything else until
+ // they enter a valid SIM network PIN.
+ Log.i(LOG_TAG, "show sim depersonal panel");
+ IccNetworkDepersonalizationPanel ndpPanel =
+ new IccNetworkDepersonalizationPanel(PhoneGlobals.getInstance());
+ ndpPanel.show();
+ }
+ break;
+
+ case EVENT_UPDATE_INCALL_NOTIFICATION:
+ // Tell the NotificationMgr to update the "ongoing
+ // call" icon in the status bar, if necessary.
+ // Currently, this is triggered by a bluetooth headset
+ // state change (since the status bar icon needs to
+ // turn blue when bluetooth is active.)
+ if (DBG) Log.d (LOG_TAG, "- updating in-call notification from handler...");
+ notificationMgr.updateInCallNotification();
+ break;
+
+ case EVENT_DATA_ROAMING_DISCONNECTED:
+ notificationMgr.showDataDisconnectedRoaming();
+ break;
+
+ case EVENT_DATA_ROAMING_OK:
+ notificationMgr.hideDataDisconnectedRoaming();
+ break;
+
+ case MMI_COMPLETE:
+ onMMIComplete((AsyncResult) msg.obj);
+ break;
+
+ case MMI_CANCEL:
+ PhoneUtils.cancelMmiCode(phone);
+ break;
+
+ case EVENT_WIRED_HEADSET_PLUG:
+ // Since the presence of a wired headset or bluetooth affects the
+ // speakerphone, update the "speaker" state. We ONLY want to do
+ // this on the wired headset connect / disconnect events for now
+ // though, so we're only triggering on EVENT_WIRED_HEADSET_PLUG.
+
+ phoneState = mCM.getState();
+ // Do not change speaker state if phone is not off hook
+ if (phoneState == PhoneConstants.State.OFFHOOK && !isBluetoothHeadsetAudioOn()) {
+ if (!isHeadsetPlugged()) {
+ // if the state is "not connected", restore the speaker state.
+ PhoneUtils.restoreSpeakerMode(getApplicationContext());
+ } else {
+ // if the state is "connected", force the speaker off without
+ // storing the state.
+ PhoneUtils.turnOnSpeaker(getApplicationContext(), false, false);
+ }
+ }
+ // Update the Proximity sensor based on headset state
+ updateProximitySensorMode(phoneState);
+
+ // Force TTY state update according to new headset state
+ if (mTtyEnabled) {
+ sendMessage(obtainMessage(EVENT_TTY_PREFERRED_MODE_CHANGED, 0));
+ }
+ break;
+
+ case EVENT_SIM_STATE_CHANGED:
+ // Marks the event where the SIM goes into ready state.
+ // Right now, this is only used for the PUK-unlocking
+ // process.
+ if (msg.obj.equals(IccCardConstants.INTENT_VALUE_ICC_READY)) {
+ // when the right event is triggered and there
+ // are UI objects in the foreground, we close
+ // them to display the lock panel.
+ if (mPUKEntryActivity != null) {
+ mPUKEntryActivity.finish();
+ mPUKEntryActivity = null;
+ }
+ if (mPUKEntryProgressDialog != null) {
+ mPUKEntryProgressDialog.dismiss();
+ mPUKEntryProgressDialog = null;
+ }
+ }
+ break;
+
+ case EVENT_UNSOL_CDMA_INFO_RECORD:
+ //TODO: handle message here;
+ break;
+
+ case EVENT_DOCK_STATE_CHANGED:
+ // If the phone is docked/undocked during a call, and no wired or BT headset
+ // is connected: turn on/off the speaker accordingly.
+ boolean inDockMode = false;
+ if (mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
+ inDockMode = true;
+ }
+ if (VDBG) Log.d(LOG_TAG, "received EVENT_DOCK_STATE_CHANGED. Phone inDock = "
+ + inDockMode);
+
+ phoneState = mCM.getState();
+ if (phoneState == PhoneConstants.State.OFFHOOK &&
+ !isHeadsetPlugged() && !isBluetoothHeadsetAudioOn()) {
+ PhoneUtils.turnOnSpeaker(getApplicationContext(), inDockMode, true);
+ updateInCallScreen(); // Has no effect if the InCallScreen isn't visible
+ }
+ break;
+
+ case EVENT_TTY_PREFERRED_MODE_CHANGED:
+ // TTY mode is only applied if a headset is connected
+ int ttyMode;
+ if (isHeadsetPlugged()) {
+ ttyMode = mPreferredTtyMode;
+ } else {
+ ttyMode = Phone.TTY_MODE_OFF;
+ }
+ phone.setTTYMode(ttyMode, mHandler.obtainMessage(EVENT_TTY_MODE_SET));
+ break;
+
+ case EVENT_TTY_MODE_GET:
+ handleQueryTTYModeResponse(msg);
+ break;
+
+ case EVENT_TTY_MODE_SET:
+ handleSetTTYModeResponse(msg);
+ break;
+ }
+ }
+ };
+
+ public PhoneGlobals(Context context) {
+ super(context);
+ sMe = this;
+ }
+
+ public void onCreate() {
+ if (VDBG) Log.v(LOG_TAG, "onCreate()...");
+
+ ContentResolver resolver = getContentResolver();
+
+ // Cache the "voice capable" flag.
+ // This flag currently comes from a resource (which is
+ // overrideable on a per-product basis):
+ sVoiceCapable =
+ getResources().getBoolean(com.android.internal.R.bool.config_voice_capable);
+ // ...but this might eventually become a PackageManager "system
+ // feature" instead, in which case we'd do something like:
+ // sVoiceCapable =
+ // getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY_VOICE_CALLS);
+
+ if (phone == null) {
+ // Initialize the telephony framework
+ PhoneFactory.makeDefaultPhones(this);
+
+ // Get the default phone
+ phone = PhoneFactory.getDefaultPhone();
+
+ // Start TelephonyDebugService After the default phone is created.
+ Intent intent = new Intent(this, TelephonyDebugService.class);
+ startService(intent);
+
+ mCM = CallManager.getInstance();
+ mCM.registerPhone(phone);
+
+ // Create the NotificationMgr singleton, which is used to display
+ // status bar icons and control other status bar behavior.
+ notificationMgr = NotificationMgr.init(this);
+
+ phoneMgr = PhoneInterfaceManager.init(this, phone);
+
+ mHandler.sendEmptyMessage(EVENT_START_SIP_SERVICE);
+
+ int phoneType = phone.getPhoneType();
+
+ if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+ // Create an instance of CdmaPhoneCallState and initialize it to IDLE
+ cdmaPhoneCallState = new CdmaPhoneCallState();
+ cdmaPhoneCallState.CdmaPhoneCallStateInit();
+ }
+
+ if (BluetoothAdapter.getDefaultAdapter() != null) {
+ // Start BluetoothPhoneService even if device is not voice capable.
+ // The device can still support VOIP.
+ startService(new Intent(this, BluetoothPhoneService.class));
+ bindService(new Intent(this, BluetoothPhoneService.class),
+ mBluetoothPhoneConnection, 0);
+ } else {
+ // Device is not bluetooth capable
+ mBluetoothPhone = null;
+ }
+
+ ringer = Ringer.init(this);
+
+ // before registering for phone state changes
+ mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
+ mWakeLock = mPowerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, LOG_TAG);
+ // lock used to keep the processor awake, when we don't care for the display.
+ mPartialWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK
+ | PowerManager.ON_AFTER_RELEASE, LOG_TAG);
+ // Wake lock used to control proximity sensor behavior.
+ if (mPowerManager.isWakeLockLevelSupported(
+ PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {
+ mProximityWakeLock = mPowerManager.newWakeLock(
+ PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, LOG_TAG);
+ }
+ if (DBG) Log.d(LOG_TAG, "onCreate: mProximityWakeLock: " + mProximityWakeLock);
+
+ // create mAccelerometerListener only if we are using the proximity sensor
+ if (proximitySensorModeEnabled()) {
+ mAccelerometerListener = new AccelerometerListener(this, this);
+ }
+
+ mKeyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
+
+ // get a handle to the service so that we can use it later when we
+ // want to set the poke lock.
+ mPowerManagerService = IPowerManager.Stub.asInterface(
+ ServiceManager.getService("power"));
+
+ // Get UpdateLock to suppress system-update related events (e.g. dialog show-up)
+ // during phone calls.
+ mUpdateLock = new UpdateLock("phone");
+
+ if (DBG) Log.d(LOG_TAG, "onCreate: mUpdateLock: " + mUpdateLock);
+
+ CallLogger callLogger = new CallLogger(this, new CallLogAsync());
+
+ // Create the CallController singleton, which is the interface
+ // to the telephony layer for user-initiated telephony functionality
+ // (like making outgoing calls.)
+ callController = CallController.init(this, callLogger);
+ // ...and also the InCallUiState instance, used by the CallController to
+ // keep track of some "persistent state" of the in-call UI.
+ inCallUiState = InCallUiState.init(this);
+
+ // Create the CallerInfoCache singleton, which remembers custom ring tone and
+ // send-to-voicemail settings.
+ //
+ // The asynchronous caching will start just after this call.
+ callerInfoCache = CallerInfoCache.init(this);
+
+ // Monitors call activity from the telephony layer
+ callStateMonitor = new CallStateMonitor(mCM);
+
+ // Create the CallNotifer singleton, which handles
+ // asynchronous events from the telephony layer (like
+ // launching the incoming-call UI when an incoming call comes
+ // in.)
+ notifier = CallNotifier.init(this, phone, ringer, callLogger, callStateMonitor);
+
+ // register for ICC status
+ IccCard sim = phone.getIccCard();
+ if (sim != null) {
+ if (VDBG) Log.v(LOG_TAG, "register for ICC status");
+ sim.registerForNetworkLocked(mHandler, EVENT_SIM_NETWORK_LOCKED, null);
+ }
+
+ // register for MMI/USSD
+ mCM.registerForMmiComplete(mHandler, MMI_COMPLETE, null);
+
+ // register connection tracking to PhoneUtils
+ PhoneUtils.initializeConnectionHandler(mCM);
+
+ // Read platform settings for TTY feature
+ mTtyEnabled = getResources().getBoolean(R.bool.tty_enabled);
+
+ // Register for misc other intent broadcasts.
+ IntentFilter intentFilter =
+ new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ intentFilter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
+ intentFilter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
+ intentFilter.addAction(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
+ intentFilter.addAction(Intent.ACTION_HEADSET_PLUG);
+ intentFilter.addAction(Intent.ACTION_DOCK_EVENT);
+ intentFilter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
+ intentFilter.addAction(TelephonyIntents.ACTION_RADIO_TECHNOLOGY_CHANGED);
+ intentFilter.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
+ intentFilter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
+ if (mTtyEnabled) {
+ intentFilter.addAction(TtyIntent.TTY_PREFERRED_MODE_CHANGE_ACTION);
+ }
+ intentFilter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
+ registerReceiver(mReceiver, intentFilter);
+
+ // Use a separate receiver for ACTION_MEDIA_BUTTON broadcasts,
+ // since we need to manually adjust its priority (to make sure
+ // we get these intents *before* the media player.)
+ IntentFilter mediaButtonIntentFilter =
+ new IntentFilter(Intent.ACTION_MEDIA_BUTTON);
+ // TODO verify the independent priority doesn't need to be handled thanks to the
+ // private intent handler registration
+ // Make sure we're higher priority than the media player's
+ // MediaButtonIntentReceiver (which currently has the default
+ // priority of zero; see apps/Music/AndroidManifest.xml.)
+ mediaButtonIntentFilter.setPriority(1);
+ //
+ registerReceiver(mMediaButtonReceiver, mediaButtonIntentFilter);
+ // register the component so it gets priority for calls
+ AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ am.registerMediaButtonEventReceiverForCalls(new ComponentName(this.getPackageName(),
+ MediaButtonBroadcastReceiver.class.getName()));
+
+ //set the default values for the preferences in the phone.
+ PreferenceManager.setDefaultValues(this, R.xml.network_setting, false);
+
+ PreferenceManager.setDefaultValues(this, R.xml.call_feature_setting, false);
+
+ // Make sure the audio mode (along with some
+ // audio-mode-related state of our own) is initialized
+ // correctly, given the current state of the phone.
+ PhoneUtils.setAudioMode(mCM);
+ }
+
+ if (TelephonyCapabilities.supportsOtasp(phone)) {
+ cdmaOtaProvisionData = new OtaUtils.CdmaOtaProvisionData();
+ cdmaOtaConfigData = new OtaUtils.CdmaOtaConfigData();
+ cdmaOtaScreenState = new OtaUtils.CdmaOtaScreenState();
+ cdmaOtaInCallScreenUiState = new OtaUtils.CdmaOtaInCallScreenUiState();
+ }
+
+ // XXX pre-load the SimProvider so that it's ready
+ resolver.getType(Uri.parse("content://icc/adn"));
+
+ // start with the default value to set the mute state.
+ mShouldRestoreMuteOnInCallResume = false;
+
+ // TODO: Register for Cdma Information Records
+ // phone.registerCdmaInformationRecord(mHandler, EVENT_UNSOL_CDMA_INFO_RECORD, null);
+
+ // Read TTY settings and store it into BP NV.
+ // AP owns (i.e. stores) the TTY setting in AP settings database and pushes the setting
+ // to BP at power up (BP does not need to make the TTY setting persistent storage).
+ // This way, there is a single owner (i.e AP) for the TTY setting in the phone.
+ if (mTtyEnabled) {
+ mPreferredTtyMode = android.provider.Settings.Secure.getInt(
+ phone.getContext().getContentResolver(),
+ android.provider.Settings.Secure.PREFERRED_TTY_MODE,
+ Phone.TTY_MODE_OFF);
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_TTY_PREFERRED_MODE_CHANGED, 0));
+ }
+ // Read HAC settings and configure audio hardware
+ if (getResources().getBoolean(R.bool.hac_enabled)) {
+ int hac = android.provider.Settings.System.getInt(phone.getContext().getContentResolver(),
+ android.provider.Settings.System.HEARING_AID,
+ 0);
+ AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ audioManager.setParameter(CallFeaturesSetting.HAC_KEY, hac != 0 ?
+ CallFeaturesSetting.HAC_VAL_ON :
+ CallFeaturesSetting.HAC_VAL_OFF);
+ }
+ }
+
+ public void onConfigurationChanged(Configuration newConfig) {
+ if (newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO) {
+ mIsHardKeyboardOpen = true;
+ } else {
+ mIsHardKeyboardOpen = false;
+ }
+
+ // Update the Proximity sensor based on keyboard state
+ updateProximitySensorMode(mCM.getState());
+ }
+
+ /**
+ * Returns the singleton instance of the PhoneApp.
+ */
+ static PhoneGlobals getInstance() {
+ if (sMe == null) {
+ throw new IllegalStateException("No PhoneGlobals here!");
+ }
+ return sMe;
+ }
+
+ /**
+ * Returns the singleton instance of the PhoneApp if running as the
+ * primary user, otherwise null.
+ */
+ static PhoneGlobals getInstanceIfPrimary() {
+ return sMe;
+ }
+
+ /**
+ * Returns the Phone associated with this instance
+ */
+ static Phone getPhone() {
+ return getInstance().phone;
+ }
+
+ Ringer getRinger() {
+ return ringer;
+ }
+
+ IBluetoothHeadsetPhone getBluetoothPhoneService() {
+ return mBluetoothPhone;
+ }
+
+ boolean isBluetoothHeadsetAudioOn() {
+ return (mBluetoothHeadsetAudioState != BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+ }
+
+ /**
+ * Returns an Intent that can be used to go to the "Call log"
+ * UI (aka CallLogActivity) in the Contacts app.
+ *
+ * Watch out: there's no guarantee that the system has any activity to
+ * handle this intent. (In particular there may be no "Call log" at
+ * all on on non-voice-capable devices.)
+ */
+ /* package */ static Intent createCallLogIntent() {
+ Intent intent = new Intent(Intent.ACTION_VIEW, null);
+ intent.setType("vnd.android.cursor.dir/calls");
+ return intent;
+ }
+
+ /**
+ * Return an Intent that can be used to bring up the in-call screen.
+ *
+ * This intent can only be used from within the Phone app, since the
+ * InCallScreen is not exported from our AndroidManifest.
+ */
+ /* package */ static Intent createInCallIntent() {
+ Intent intent = new Intent(Intent.ACTION_MAIN, null);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
+ | Intent.FLAG_ACTIVITY_NO_USER_ACTION);
+ intent.setClassName("com.android.phone", getCallScreenClassName());
+ return intent;
+ }
+
+ /**
+ * Variation of createInCallIntent() that also specifies whether the
+ * DTMF dialpad should be initially visible when the InCallScreen
+ * comes up.
+ */
+ /* package */ static Intent createInCallIntent(boolean showDialpad) {
+ Intent intent = createInCallIntent();
+ intent.putExtra(InCallScreen.SHOW_DIALPAD_EXTRA, showDialpad);
+ return intent;
+ }
+
+ /**
+ * Returns PendingIntent for hanging up ongoing phone call. This will typically be used from
+ * Notification context.
+ */
+ /* package */ static PendingIntent createHangUpOngoingCallPendingIntent(Context context) {
+ Intent intent = new Intent(PhoneGlobals.ACTION_HANG_UP_ONGOING_CALL, null,
+ context, NotificationBroadcastReceiver.class);
+ return PendingIntent.getBroadcast(context, 0, intent, 0);
+ }
+
+ /* package */ static PendingIntent getCallBackPendingIntent(Context context, String number) {
+ Intent intent = new Intent(ACTION_CALL_BACK_FROM_NOTIFICATION,
+ Uri.fromParts(Constants.SCHEME_TEL, number, null),
+ context, NotificationBroadcastReceiver.class);
+ return PendingIntent.getBroadcast(context, 0, intent, 0);
+ }
+
+ /* package */ static PendingIntent getSendSmsFromNotificationPendingIntent(
+ Context context, String number) {
+ Intent intent = new Intent(ACTION_SEND_SMS_FROM_NOTIFICATION,
+ Uri.fromParts(Constants.SCHEME_SMSTO, number, null),
+ context, NotificationBroadcastReceiver.class);
+ return PendingIntent.getBroadcast(context, 0, intent, 0);
+ }
+
+ private static String getCallScreenClassName() {
+ return InCallScreen.class.getName();
+ }
+
+ /**
+ * Starts the InCallScreen Activity.
+ */
+ /* package */ void displayCallScreen() {
+ if (VDBG) Log.d(LOG_TAG, "displayCallScreen()...");
+
+ // On non-voice-capable devices we shouldn't ever be trying to
+ // bring up the InCallScreen in the first place.
+ if (!sVoiceCapable) {
+ Log.w(LOG_TAG, "displayCallScreen() not allowed: non-voice-capable device",
+ new Throwable("stack dump")); // Include a stack trace since this warning
+ // indicates a bug in our caller
+ return;
+ }
+
+ try {
+ startActivity(createInCallIntent());
+ } catch (ActivityNotFoundException e) {
+ // It's possible that the in-call UI might not exist (like on
+ // non-voice-capable devices), so don't crash if someone
+ // accidentally tries to bring it up...
+ Log.w(LOG_TAG, "displayCallScreen: transition to InCallScreen failed: " + e);
+ }
+ Profiler.callScreenRequested();
+ }
+
+ boolean isSimPinEnabled() {
+ return mIsSimPinEnabled;
+ }
+
+ boolean authenticateAgainstCachedSimPin(String pin) {
+ return (mCachedSimPin != null && mCachedSimPin.equals(pin));
+ }
+
+ void setCachedSimPin(String pin) {
+ mCachedSimPin = pin;
+ }
+
+ void setInCallScreenInstance(InCallScreen inCallScreen) {
+ mInCallScreen = inCallScreen;
+ }
+
+ /**
+ * @return true if the in-call UI is running as the foreground
+ * activity. (In other words, from the perspective of the
+ * InCallScreen activity, return true between onResume() and
+ * onPause().)
+ *
+ * Note this method will return false if the screen is currently off,
+ * even if the InCallScreen *was* in the foreground just before the
+ * screen turned off. (This is because the foreground activity is
+ * always "paused" while the screen is off.)
+ */
+ boolean isShowingCallScreen() {
+ if (mInCallScreen == null) return false;
+ return mInCallScreen.isForegroundActivity();
+ }
+
+ /**
+ * @return true if the in-call UI is running as the foreground activity, or,
+ * it went to background due to screen being turned off. This might be useful
+ * to determine if the in-call screen went to background because of other
+ * activities, or its proximity sensor state or manual power-button press.
+ *
+ * Here are some examples.
+ *
+ * - If you want to know if the activity is in foreground or screen is turned off
+ * from the in-call UI (i.e. though it is not "foreground" anymore it will become
+ * so after screen being turned on), check
+ * {@link #isShowingCallScreenForProximity()} is true or not.
+ * {@link #updateProximitySensorMode(com.android.internal.telephony.PhoneConstants.State)} is
+ * doing this.
+ *
+ * - If you want to know if the activity is not in foreground just because screen
+ * is turned off (not due to other activity's interference), check
+ * {@link #isShowingCallScreen()} is false *and* {@link #isShowingCallScreenForProximity()}
+ * is true. InCallScreen#onDisconnect() is doing this check.
+ *
+ * @see #isShowingCallScreen()
+ *
+ * TODO: come up with better naming..
+ */
+ boolean isShowingCallScreenForProximity() {
+ if (mInCallScreen == null) return false;
+ return mInCallScreen.isForegroundActivityForProximity();
+ }
+
+ /**
+ * Dismisses the in-call UI.
+ *
+ * This also ensures that you won't be able to get back to the in-call
+ * UI via the BACK button (since this call removes the InCallScreen
+ * from the activity history.)
+ * For OTA Call, it call InCallScreen api to handle OTA Call End scenario
+ * to display OTA Call End screen.
+ */
+ /* package */ void dismissCallScreen() {
+ if (mInCallScreen != null) {
+ if ((TelephonyCapabilities.supportsOtasp(phone)) &&
+ (mInCallScreen.isOtaCallInActiveState()
+ || mInCallScreen.isOtaCallInEndState()
+ || ((cdmaOtaScreenState != null)
+ && (cdmaOtaScreenState.otaScreenState
+ != CdmaOtaScreenState.OtaScreenState.OTA_STATUS_UNDEFINED)))) {
+ // TODO: During OTA Call, display should not become dark to
+ // allow user to see OTA UI update. Phone app needs to hold
+ // a SCREEN_DIM_WAKE_LOCK wake lock during the entire OTA call.
+ wakeUpScreen();
+ // If InCallScreen is not in foreground we resume it to show the OTA call end screen
+ // Fire off the InCallScreen intent
+ displayCallScreen();
+
+ mInCallScreen.handleOtaCallEnd();
+ return;
+ } else {
+ mInCallScreen.finish();
+ }
+ }
+ }
+
+ /**
+ * Handles OTASP-related events from the telephony layer.
+ *
+ * While an OTASP call is active, the CallNotifier forwards
+ * OTASP-related telephony events to this method.
+ */
+ void handleOtaspEvent(Message msg) {
+ if (DBG) Log.d(LOG_TAG, "handleOtaspEvent(message " + msg + ")...");
+
+ if (otaUtils == null) {
+ // We shouldn't be getting OTASP events without ever
+ // having started the OTASP call in the first place!
+ Log.w(LOG_TAG, "handleOtaEvents: got an event but otaUtils is null! "
+ + "message = " + msg);
+ return;
+ }
+
+ otaUtils.onOtaProvisionStatusChanged((AsyncResult) msg.obj);
+ }
+
+ /**
+ * Similarly, handle the disconnect event of an OTASP call
+ * by forwarding it to the OtaUtils instance.
+ */
+ /* package */ void handleOtaspDisconnect() {
+ if (DBG) Log.d(LOG_TAG, "handleOtaspDisconnect()...");
+
+ if (otaUtils == null) {
+ // We shouldn't be getting OTASP events without ever
+ // having started the OTASP call in the first place!
+ Log.w(LOG_TAG, "handleOtaspDisconnect: otaUtils is null!");
+ return;
+ }
+
+ otaUtils.onOtaspDisconnect();
+ }
+
+ /**
+ * Sets the activity responsible for un-PUK-blocking the device
+ * so that we may close it when we receive a positive result.
+ * mPUKEntryActivity is also used to indicate to the device that
+ * we are trying to un-PUK-lock the phone. In other words, iff
+ * it is NOT null, then we are trying to unlock and waiting for
+ * the SIM to move to READY state.
+ *
+ * @param activity is the activity to close when PUK has
+ * finished unlocking. Can be set to null to indicate the unlock
+ * or SIM READYing process is over.
+ */
+ void setPukEntryActivity(Activity activity) {
+ mPUKEntryActivity = activity;
+ }
+
+ Activity getPUKEntryActivity() {
+ return mPUKEntryActivity;
+ }
+
+ /**
+ * Sets the dialog responsible for notifying the user of un-PUK-
+ * blocking - SIM READYing progress, so that we may dismiss it
+ * when we receive a positive result.
+ *
+ * @param dialog indicates the progress dialog informing the user
+ * of the state of the device. Dismissed upon completion of
+ * READYing process
+ */
+ void setPukEntryProgressDialog(ProgressDialog dialog) {
+ mPUKEntryProgressDialog = dialog;
+ }
+
+ ProgressDialog getPUKEntryProgressDialog() {
+ return mPUKEntryProgressDialog;
+ }
+
+ /**
+ * Controls whether or not the screen is allowed to sleep.
+ *
+ * Once sleep is allowed (WakeState is SLEEP), it will rely on the
+ * settings for the poke lock to determine when to timeout and let
+ * the device sleep {@link PhoneGlobals#setScreenTimeout}.
+ *
+ * @param ws tells the device to how to wake.
+ */
+ /* package */ void requestWakeState(WakeState ws) {
+ if (VDBG) Log.d(LOG_TAG, "requestWakeState(" + ws + ")...");
+ synchronized (this) {
+ if (mWakeState != ws) {
+ switch (ws) {
+ case PARTIAL:
+ // acquire the processor wake lock, and release the FULL
+ // lock if it is being held.
+ mPartialWakeLock.acquire();
+ if (mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
+ break;
+ case FULL:
+ // acquire the full wake lock, and release the PARTIAL
+ // lock if it is being held.
+ mWakeLock.acquire();
+ if (mPartialWakeLock.isHeld()) {
+ mPartialWakeLock.release();
+ }
+ break;
+ case SLEEP:
+ default:
+ // release both the PARTIAL and FULL locks.
+ if (mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
+ if (mPartialWakeLock.isHeld()) {
+ mPartialWakeLock.release();
+ }
+ break;
+ }
+ mWakeState = ws;
+ }
+ }
+ }
+
+ /**
+ * If we are not currently keeping the screen on, then poke the power
+ * manager to wake up the screen for the user activity timeout duration.
+ */
+ /* package */ void wakeUpScreen() {
+ synchronized (this) {
+ if (mWakeState == WakeState.SLEEP) {
+ if (DBG) Log.d(LOG_TAG, "pulse screen lock");
+ mPowerManager.wakeUp(SystemClock.uptimeMillis());
+ }
+ }
+ }
+
+ /**
+ * Sets the wake state and screen timeout based on the current state
+ * of the phone, and the current state of the in-call UI.
+ *
+ * This method is a "UI Policy" wrapper around
+ * {@link PhoneGlobals#requestWakeState} and {@link PhoneGlobals#setScreenTimeout}.
+ *
+ * It's safe to call this method regardless of the state of the Phone
+ * (e.g. whether or not it's idle), and regardless of the state of the
+ * Phone UI (e.g. whether or not the InCallScreen is active.)
+ */
+ /* package */ void updateWakeState() {
+ PhoneConstants.State state = mCM.getState();
+
+ // True if the in-call UI is the foreground activity.
+ // (Note this will be false if the screen is currently off,
+ // since in that case *no* activity is in the foreground.)
+ boolean isShowingCallScreen = isShowingCallScreen();
+
+ // True if the InCallScreen's DTMF dialer is currently opened.
+ // (Note this does NOT imply whether or not the InCallScreen
+ // itself is visible.)
+ boolean isDialerOpened = (mInCallScreen != null) && mInCallScreen.isDialerOpened();
+
+ // True if the speakerphone is in use. (If so, we *always* use
+ // the default timeout. Since the user is obviously not holding
+ // the phone up to his/her face, we don't need to worry about
+ // false touches, and thus don't need to turn the screen off so
+ // aggressively.)
+ // Note that we need to make a fresh call to this method any
+ // time the speaker state changes. (That happens in
+ // PhoneUtils.turnOnSpeaker().)
+ boolean isSpeakerInUse = (state == PhoneConstants.State.OFFHOOK) && PhoneUtils.isSpeakerOn(this);
+
+ // TODO (bug 1440854): The screen timeout *might* also need to
+ // depend on the bluetooth state, but this isn't as clear-cut as
+ // the speaker state (since while using BT it's common for the
+ // user to put the phone straight into a pocket, in which case the
+ // timeout should probably still be short.)
+
+ if (DBG) Log.d(LOG_TAG, "updateWakeState: callscreen " + isShowingCallScreen
+ + ", dialer " + isDialerOpened
+ + ", speaker " + isSpeakerInUse + "...");
+
+ //
+ // Decide whether to force the screen on or not.
+ //
+ // Force the screen to be on if the phone is ringing or dialing,
+ // or if we're displaying the "Call ended" UI for a connection in
+ // the "disconnected" state.
+ // However, if the phone is disconnected while the user is in the
+ // middle of selecting a quick response message, we should not force
+ // the screen to be on.
+ //
+ boolean isRinging = (state == PhoneConstants.State.RINGING);
+ boolean isDialing = (phone.getForegroundCall().getState() == Call.State.DIALING);
+ boolean showingQuickResponseDialog = (mInCallScreen != null) &&
+ mInCallScreen.isQuickResponseDialogShowing();
+ boolean showingDisconnectedConnection =
+ PhoneUtils.hasDisconnectedConnections(phone) && isShowingCallScreen;
+ boolean keepScreenOn = isRinging || isDialing ||
+ (showingDisconnectedConnection && !showingQuickResponseDialog);
+ if (DBG) Log.d(LOG_TAG, "updateWakeState: keepScreenOn = " + keepScreenOn
+ + " (isRinging " + isRinging
+ + ", isDialing " + isDialing
+ + ", showingQuickResponse " + showingQuickResponseDialog
+ + ", showingDisc " + showingDisconnectedConnection + ")");
+ // keepScreenOn == true means we'll hold a full wake lock:
+ requestWakeState(keepScreenOn ? WakeState.FULL : WakeState.SLEEP);
+ }
+
+ /**
+ * Manually pokes the PowerManager's userActivity method. Since we
+ * set the {@link WindowManager.LayoutParams#INPUT_FEATURE_DISABLE_USER_ACTIVITY}
+ * flag while the InCallScreen is active when there is no proximity sensor,
+ * we need to do this for touch events that really do count as user activity
+ * (like pressing any onscreen UI elements.)
+ */
+ /* package */ void pokeUserActivity() {
+ if (VDBG) Log.d(LOG_TAG, "pokeUserActivity()...");
+ mPowerManager.userActivity(SystemClock.uptimeMillis(), false);
+ }
+
+ /**
+ * Set when a new outgoing call is beginning, so we can update
+ * the proximity sensor state.
+ * Cleared when the InCallScreen is no longer in the foreground,
+ * in case the call fails without changing the telephony state.
+ */
+ /* package */ void setBeginningCall(boolean beginning) {
+ // Note that we are beginning a new call, for proximity sensor support
+ mBeginningCall = beginning;
+ // Update the Proximity sensor based on mBeginningCall state
+ updateProximitySensorMode(mCM.getState());
+ }
+
+ /**
+ * Updates the wake lock used to control proximity sensor behavior,
+ * based on the current state of the phone. This method is called
+ * from the CallNotifier on any phone state change.
+ *
+ * On devices that have a proximity sensor, to avoid false touches
+ * during a call, we hold a PROXIMITY_SCREEN_OFF_WAKE_LOCK wake lock
+ * whenever the phone is off hook. (When held, that wake lock causes
+ * the screen to turn off automatically when the sensor detects an
+ * object close to the screen.)
+ *
+ * This method is a no-op for devices that don't have a proximity
+ * sensor.
+ *
+ * Note this method doesn't care if the InCallScreen is the foreground
+ * activity or not. That's because we want the proximity sensor to be
+ * enabled any time the phone is in use, to avoid false cheek events
+ * for whatever app you happen to be running.
+ *
+ * Proximity wake lock will *not* be held if any one of the
+ * conditions is true while on a call:
+ * 1) If the audio is routed via Bluetooth
+ * 2) If a wired headset is connected
+ * 3) if the speaker is ON
+ * 4) If the slider is open(i.e. the hardkeyboard is *not* hidden)
+ *
+ * @param state current state of the phone (see {@link Phone#State})
+ */
+ /* package */ void updateProximitySensorMode(PhoneConstants.State state) {
+ if (VDBG) Log.d(LOG_TAG, "updateProximitySensorMode: state = " + state);
+
+ if (proximitySensorModeEnabled()) {
+ synchronized (mProximityWakeLock) {
+ // turn proximity sensor off and turn screen on immediately if
+ // we are using a headset, the keyboard is open, or the device
+ // is being held in a horizontal position.
+ boolean screenOnImmediately = (isHeadsetPlugged()
+ || PhoneUtils.isSpeakerOn(this)
+ || isBluetoothHeadsetAudioOn()
+ || mIsHardKeyboardOpen);
+
+ // We do not keep the screen off when the user is outside in-call screen and we are
+ // horizontal, but we do not force it on when we become horizontal until the
+ // proximity sensor goes negative.
+ boolean horizontal =
+ (mOrientation == AccelerometerListener.ORIENTATION_HORIZONTAL);
+ screenOnImmediately |= !isShowingCallScreenForProximity() && horizontal;
+
+ // We do not keep the screen off when dialpad is visible, we are horizontal, and
+ // the in-call screen is being shown.
+ // At that moment we're pretty sure users want to use it, instead of letting the
+ // proximity sensor turn off the screen by their hands.
+ boolean dialpadVisible = false;
+ if (mInCallScreen != null) {
+ dialpadVisible =
+ mInCallScreen.getUpdatedInCallControlState().dialpadEnabled
+ && mInCallScreen.getUpdatedInCallControlState().dialpadVisible
+ && isShowingCallScreen();
+ }
+ screenOnImmediately |= dialpadVisible && horizontal;
+
+ if (((state == PhoneConstants.State.OFFHOOK) || mBeginningCall) && !screenOnImmediately) {
+ // Phone is in use! Arrange for the screen to turn off
+ // automatically when the sensor detects a close object.
+ if (!mProximityWakeLock.isHeld()) {
+ if (DBG) Log.d(LOG_TAG, "updateProximitySensorMode: acquiring...");
+ mProximityWakeLock.acquire();
+ } else {
+ if (VDBG) Log.d(LOG_TAG, "updateProximitySensorMode: lock already held.");
+ }
+ } else {
+ // Phone is either idle, or ringing. We don't want any
+ // special proximity sensor behavior in either case.
+ if (mProximityWakeLock.isHeld()) {
+ if (DBG) Log.d(LOG_TAG, "updateProximitySensorMode: releasing...");
+ // Wait until user has moved the phone away from his head if we are
+ // releasing due to the phone call ending.
+ // Qtherwise, turn screen on immediately
+ int flags =
+ (screenOnImmediately ? 0 : PowerManager.WAIT_FOR_PROXIMITY_NEGATIVE);
+ mProximityWakeLock.release(flags);
+ } else {
+ if (VDBG) {
+ Log.d(LOG_TAG, "updateProximitySensorMode: lock already released.");
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void orientationChanged(int orientation) {
+ mOrientation = orientation;
+ updateProximitySensorMode(mCM.getState());
+ }
+
+ /**
+ * Notifies the phone app when the phone state changes.
+ *
+ * This method will updates various states inside Phone app (e.g. proximity sensor mode,
+ * accelerometer listener state, update-lock state, etc.)
+ */
+ /* package */ void updatePhoneState(PhoneConstants.State state) {
+ if (state != mLastPhoneState) {
+ mLastPhoneState = state;
+ updateProximitySensorMode(state);
+
+ // Try to acquire or release UpdateLock.
+ //
+ // Watch out: we don't release the lock here when the screen is still in foreground.
+ // At that time InCallScreen will release it on onPause().
+ if (state != PhoneConstants.State.IDLE) {
+ // UpdateLock is a recursive lock, while we may get "acquire" request twice and
+ // "release" request once for a single call (RINGING + OFFHOOK and IDLE).
+ // We need to manually ensure the lock is just acquired once for each (and this
+ // will prevent other possible buggy situations too).
+ if (!mUpdateLock.isHeld()) {
+ mUpdateLock.acquire();
+ }
+ } else {
+ if (!isShowingCallScreen()) {
+ if (!mUpdateLock.isHeld()) {
+ mUpdateLock.release();
+ }
+ } else {
+ // For this case InCallScreen will take care of the release() call.
+ }
+ }
+
+ if (mAccelerometerListener != null) {
+ // use accelerometer to augment proximity sensor when in call
+ mOrientation = AccelerometerListener.ORIENTATION_UNKNOWN;
+ mAccelerometerListener.enable(state == PhoneConstants.State.OFFHOOK);
+ }
+ // clear our beginning call flag
+ mBeginningCall = false;
+ // 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.
+ if (mInCallScreen != null) {
+ mInCallScreen.updateKeyguardPolicy(state == PhoneConstants.State.OFFHOOK);
+ }
+ }
+ }
+
+ /* package */ PhoneConstants.State getPhoneState() {
+ return mLastPhoneState;
+ }
+
+ /**
+ * Returns UpdateLock object.
+ */
+ /* package */ UpdateLock getUpdateLock() {
+ return mUpdateLock;
+ }
+
+ /**
+ * @return true if this device supports the "proximity sensor
+ * auto-lock" feature while in-call (see updateProximitySensorMode()).
+ */
+ /* package */ boolean proximitySensorModeEnabled() {
+ return (mProximityWakeLock != null);
+ }
+
+ KeyguardManager getKeyguardManager() {
+ return mKeyguardManager;
+ }
+
+ private void onMMIComplete(AsyncResult r) {
+ if (VDBG) Log.d(LOG_TAG, "onMMIComplete()...");
+ MmiCode mmiCode = (MmiCode) r.result;
+ PhoneUtils.displayMMIComplete(phone, getInstance(), mmiCode, null, null);
+ }
+
+ private void initForNewRadioTechnology() {
+ if (DBG) Log.d(LOG_TAG, "initForNewRadioTechnology...");
+
+ if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
+ // Create an instance of CdmaPhoneCallState and initialize it to IDLE
+ cdmaPhoneCallState = new CdmaPhoneCallState();
+ cdmaPhoneCallState.CdmaPhoneCallStateInit();
+ }
+ if (TelephonyCapabilities.supportsOtasp(phone)) {
+ //create instances of CDMA OTA data classes
+ if (cdmaOtaProvisionData == null) {
+ cdmaOtaProvisionData = new OtaUtils.CdmaOtaProvisionData();
+ }
+ if (cdmaOtaConfigData == null) {
+ cdmaOtaConfigData = new OtaUtils.CdmaOtaConfigData();
+ }
+ if (cdmaOtaScreenState == null) {
+ cdmaOtaScreenState = new OtaUtils.CdmaOtaScreenState();
+ }
+ if (cdmaOtaInCallScreenUiState == null) {
+ cdmaOtaInCallScreenUiState = new OtaUtils.CdmaOtaInCallScreenUiState();
+ }
+ } else {
+ //Clean up OTA data in GSM/UMTS. It is valid only for CDMA
+ clearOtaState();
+ }
+
+ ringer.updateRingerContextAfterRadioTechnologyChange(this.phone);
+ notifier.updateCallNotifierRegistrationsAfterRadioTechnologyChange();
+ callStateMonitor.updateAfterRadioTechnologyChange();
+
+ if (mBluetoothPhone != null) {
+ try {
+ mBluetoothPhone.updateBtHandsfreeAfterRadioTechnologyChange();
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, Log.getStackTraceString(new Throwable()));
+ }
+ }
+ if (mInCallScreen != null) {
+ mInCallScreen.updateAfterRadioTechnologyChange();
+ }
+
+ // Update registration for ICC status after radio technology change
+ IccCard sim = phone.getIccCard();
+ if (sim != null) {
+ if (DBG) Log.d(LOG_TAG, "Update registration for ICC status...");
+
+ //Register all events new to the new active phone
+ sim.registerForNetworkLocked(mHandler, EVENT_SIM_NETWORK_LOCKED, null);
+ }
+ }
+
+
+ /**
+ * @return true if a wired headset is currently plugged in.
+ *
+ * @see Intent.ACTION_HEADSET_PLUG (which we listen for in mReceiver.onReceive())
+ */
+ boolean isHeadsetPlugged() {
+ return mIsHeadsetPlugged;
+ }
+
+ /**
+ * @return true if the onscreen UI should currently be showing the
+ * special "bluetooth is active" indication in a couple of places (in
+ * which UI elements turn blue and/or show the bluetooth logo.)
+ *
+ * This depends on the BluetoothHeadset state *and* the current
+ * telephony state; see shouldShowBluetoothIndication().
+ *
+ * @see CallCard
+ * @see NotificationMgr.updateInCallNotification
+ */
+ /* package */ boolean showBluetoothIndication() {
+ return mShowBluetoothIndication;
+ }
+
+ /**
+ * Recomputes the mShowBluetoothIndication flag based on the current
+ * bluetooth state and current telephony state.
+ *
+ * This needs to be called any time the bluetooth headset state or the
+ * telephony state changes.
+ *
+ * @param forceUiUpdate if true, force the UI elements that care
+ * about this flag to update themselves.
+ */
+ /* package */ void updateBluetoothIndication(boolean forceUiUpdate) {
+ mShowBluetoothIndication = shouldShowBluetoothIndication(mBluetoothHeadsetState,
+ mBluetoothHeadsetAudioState,
+ mCM);
+ if (forceUiUpdate) {
+ // Post Handler messages to the various components that might
+ // need to be refreshed based on the new state.
+ if (isShowingCallScreen()) mInCallScreen.requestUpdateBluetoothIndication();
+ if (DBG) Log.d (LOG_TAG, "- updating in-call notification for BT state change...");
+ mHandler.sendEmptyMessage(EVENT_UPDATE_INCALL_NOTIFICATION);
+ }
+
+ // Update the Proximity sensor based on Bluetooth audio state
+ updateProximitySensorMode(mCM.getState());
+ }
+
+ /**
+ * UI policy helper function for the couple of places in the UI that
+ * have some way of indicating that "bluetooth is in use."
+ *
+ * @return true if the onscreen UI should indicate that "bluetooth is in use",
+ * based on the specified bluetooth headset state, and the
+ * current state of the phone.
+ * @see showBluetoothIndication()
+ */
+ private static boolean shouldShowBluetoothIndication(int bluetoothState,
+ int bluetoothAudioState,
+ CallManager cm) {
+ // We want the UI to indicate that "bluetooth is in use" in two
+ // slightly different cases:
+ //
+ // (a) The obvious case: if a bluetooth headset is currently in
+ // use for an ongoing call.
+ //
+ // (b) The not-so-obvious case: if an incoming call is ringing,
+ // and we expect that audio *will* be routed to a bluetooth
+ // headset once the call is answered.
+
+ switch (cm.getState()) {
+ case OFFHOOK:
+ // This covers normal active calls, and also the case if
+ // the foreground call is DIALING or ALERTING. In this
+ // case, bluetooth is considered "active" if a headset
+ // is connected *and* audio is being routed to it.
+ return ((bluetoothState == BluetoothHeadset.STATE_CONNECTED)
+ && (bluetoothAudioState == BluetoothHeadset.STATE_AUDIO_CONNECTED));
+
+ case RINGING:
+ // If an incoming call is ringing, we're *not* yet routing
+ // audio to the headset (since there's no in-call audio
+ // yet!) In this case, if a bluetooth headset is
+ // connected at all, we assume that it'll become active
+ // once the user answers the phone.
+ return (bluetoothState == BluetoothHeadset.STATE_CONNECTED);
+
+ default: // Presumably IDLE
+ return false;
+ }
+ }
+
+
+ /**
+ * Receiver for misc intent broadcasts the Phone app cares about.
+ */
+ private class PhoneAppBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) {
+ boolean enabled = System.getInt(getContentResolver(),
+ System.AIRPLANE_MODE_ON, 0) == 0;
+ phone.setRadioPower(enabled);
+ } else if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
+ mBluetoothHeadsetState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
+ BluetoothHeadset.STATE_DISCONNECTED);
+ if (VDBG) Log.d(LOG_TAG, "mReceiver: HEADSET_STATE_CHANGED_ACTION");
+ if (VDBG) Log.d(LOG_TAG, "==> new state: " + mBluetoothHeadsetState);
+ updateBluetoothIndication(true); // Also update any visible UI if necessary
+ } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
+ mBluetoothHeadsetAudioState =
+ intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
+ BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+ if (VDBG) Log.d(LOG_TAG, "mReceiver: HEADSET_AUDIO_STATE_CHANGED_ACTION");
+ if (VDBG) Log.d(LOG_TAG, "==> new state: " + mBluetoothHeadsetAudioState);
+ updateBluetoothIndication(true); // Also update any visible UI if necessary
+ } else if (action.equals(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) {
+ if (VDBG) Log.d(LOG_TAG, "mReceiver: ACTION_ANY_DATA_CONNECTION_STATE_CHANGED");
+ if (VDBG) Log.d(LOG_TAG, "- state: " + intent.getStringExtra(PhoneConstants.STATE_KEY));
+ if (VDBG) Log.d(LOG_TAG, "- reason: "
+ + intent.getStringExtra(PhoneConstants.STATE_CHANGE_REASON_KEY));
+
+ // The "data disconnected due to roaming" notification is shown
+ // if (a) you have the "data roaming" feature turned off, and
+ // (b) you just lost data connectivity because you're roaming.
+ boolean disconnectedDueToRoaming =
+ !phone.getDataRoamingEnabled()
+ && "DISCONNECTED".equals(intent.getStringExtra(PhoneConstants.STATE_KEY))
+ && Phone.REASON_ROAMING_ON.equals(
+ intent.getStringExtra(PhoneConstants.STATE_CHANGE_REASON_KEY));
+ mHandler.sendEmptyMessage(disconnectedDueToRoaming
+ ? EVENT_DATA_ROAMING_DISCONNECTED
+ : EVENT_DATA_ROAMING_OK);
+ } else if (action.equals(Intent.ACTION_HEADSET_PLUG)) {
+ if (VDBG) Log.d(LOG_TAG, "mReceiver: ACTION_HEADSET_PLUG");
+ if (VDBG) Log.d(LOG_TAG, " state: " + intent.getIntExtra("state", 0));
+ if (VDBG) Log.d(LOG_TAG, " name: " + intent.getStringExtra("name"));
+ mIsHeadsetPlugged = (intent.getIntExtra("state", 0) == 1);
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_WIRED_HEADSET_PLUG, 0));
+ } else if ((action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) &&
+ (mPUKEntryActivity != null)) {
+ // if an attempt to un-PUK-lock the device was made, while we're
+ // receiving this state change notification, notify the handler.
+ // NOTE: This is ONLY triggered if an attempt to un-PUK-lock has
+ // been attempted.
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_SIM_STATE_CHANGED,
+ intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE)));
+ } else if (action.equals(TelephonyIntents.ACTION_RADIO_TECHNOLOGY_CHANGED)) {
+ String newPhone = intent.getStringExtra(PhoneConstants.PHONE_NAME_KEY);
+ Log.d(LOG_TAG, "Radio technology switched. Now " + newPhone + " is active.");
+ initForNewRadioTechnology();
+ } else if (action.equals(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED)) {
+ handleServiceStateChanged(intent);
+ } else if (action.equals(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED)) {
+ if (TelephonyCapabilities.supportsEcm(phone)) {
+ Log.d(LOG_TAG, "Emergency Callback Mode arrived in PhoneApp.");
+ // Start Emergency Callback Mode service
+ if (intent.getBooleanExtra("phoneinECMState", false)) {
+ context.startService(new Intent(context,
+ EmergencyCallbackModeService.class));
+ }
+ } else {
+ // It doesn't make sense to get ACTION_EMERGENCY_CALLBACK_MODE_CHANGED
+ // on a device that doesn't support ECM in the first place.
+ Log.e(LOG_TAG, "Got ACTION_EMERGENCY_CALLBACK_MODE_CHANGED, "
+ + "but ECM isn't supported for phone: " + phone.getPhoneName());
+ }
+ } else if (action.equals(Intent.ACTION_DOCK_EVENT)) {
+ mDockState = intent.getIntExtra(Intent.EXTRA_DOCK_STATE,
+ Intent.EXTRA_DOCK_STATE_UNDOCKED);
+ if (VDBG) Log.d(LOG_TAG, "ACTION_DOCK_EVENT -> mDockState = " + mDockState);
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_DOCK_STATE_CHANGED, 0));
+ } else if (action.equals(TtyIntent.TTY_PREFERRED_MODE_CHANGE_ACTION)) {
+ mPreferredTtyMode = intent.getIntExtra(TtyIntent.TTY_PREFFERED_MODE,
+ Phone.TTY_MODE_OFF);
+ if (VDBG) Log.d(LOG_TAG, "mReceiver: TTY_PREFERRED_MODE_CHANGE_ACTION");
+ if (VDBG) Log.d(LOG_TAG, " mode: " + mPreferredTtyMode);
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_TTY_PREFERRED_MODE_CHANGED, 0));
+ } else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
+ int ringerMode = intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE,
+ AudioManager.RINGER_MODE_NORMAL);
+ if (ringerMode == AudioManager.RINGER_MODE_SILENT) {
+ notifier.silenceRinger();
+ }
+ }
+ }
+ }
+
+ /**
+ * Broadcast receiver for the ACTION_MEDIA_BUTTON broadcast intent.
+ *
+ * This functionality isn't lumped in with the other intents in
+ * PhoneAppBroadcastReceiver because we instantiate this as a totally
+ * separate BroadcastReceiver instance, since we need to manually
+ * adjust its IntentFilter's priority (to make sure we get these
+ * intents *before* the media player.)
+ */
+ private class MediaButtonBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ KeyEvent event = (KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
+ if (VDBG) Log.d(LOG_TAG,
+ "MediaButtonBroadcastReceiver.onReceive()... event = " + event);
+ if ((event != null)
+ && (event.getKeyCode() == KeyEvent.KEYCODE_HEADSETHOOK)) {
+ if (VDBG) Log.d(LOG_TAG, "MediaButtonBroadcastReceiver: HEADSETHOOK");
+ boolean consumed = PhoneUtils.handleHeadsetHook(phone, event);
+ if (VDBG) Log.d(LOG_TAG, "==> handleHeadsetHook(): consumed = " + consumed);
+ if (consumed) {
+ // If a headset is attached and the press is consumed, also update
+ // any UI items (such as an InCallScreen mute button) that may need to
+ // be updated if their state changed.
+ updateInCallScreen(); // Has no effect if the InCallScreen isn't visible
+ abortBroadcast();
+ }
+ } else {
+ if (mCM.getState() != PhoneConstants.State.IDLE) {
+ // If the phone is anything other than completely idle,
+ // then we consume and ignore any media key events,
+ // Otherwise it is too easy to accidentally start
+ // playing music while a phone call is in progress.
+ if (VDBG) Log.d(LOG_TAG, "MediaButtonBroadcastReceiver: consumed");
+ abortBroadcast();
+ }
+ }
+ }
+ }
+
+ /**
+ * Accepts broadcast Intents which will be prepared by {@link NotificationMgr} and thus
+ * sent from framework's notification mechanism (which is outside Phone context).
+ * This should be visible from outside, but shouldn't be in "exported" state.
+ *
+ * TODO: If possible merge this into PhoneAppBroadcastReceiver.
+ */
+ public static class NotificationBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ // TODO: use "if (VDBG)" here.
+ Log.d(LOG_TAG, "Broadcast from Notification: " + action);
+
+ if (action.equals(ACTION_HANG_UP_ONGOING_CALL)) {
+ PhoneUtils.hangup(PhoneGlobals.getInstance().mCM);
+ } else if (action.equals(ACTION_CALL_BACK_FROM_NOTIFICATION)) {
+ // Collapse the expanded notification and the notification item itself.
+ closeSystemDialogs(context);
+ clearMissedCallNotification(context);
+
+ Intent callIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED, intent.getData());
+ callIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ context.startActivity(callIntent);
+ } else if (action.equals(ACTION_SEND_SMS_FROM_NOTIFICATION)) {
+ // Collapse the expanded notification and the notification item itself.
+ closeSystemDialogs(context);
+ clearMissedCallNotification(context);
+
+ Intent smsIntent = new Intent(Intent.ACTION_SENDTO, intent.getData());
+ smsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(smsIntent);
+ } else {
+ Log.w(LOG_TAG, "Received hang-up request from notification,"
+ + " but there's no call the system can hang up.");
+ }
+ }
+
+ private void closeSystemDialogs(Context context) {
+ Intent intent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+ context.sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ private void clearMissedCallNotification(Context context) {
+ Intent clearIntent = new Intent(context, ClearMissedCallsService.class);
+ clearIntent.setAction(ClearMissedCallsService.ACTION_CLEAR_MISSED_CALLS);
+ context.startService(clearIntent);
+ }
+ }
+
+ private void handleServiceStateChanged(Intent intent) {
+ /**
+ * This used to handle updating EriTextWidgetProvider this routine
+ * and and listening for ACTION_SERVICE_STATE_CHANGED intents could
+ * be removed. But leaving just in case it might be needed in the near
+ * future.
+ */
+
+ // If service just returned, start sending out the queued messages
+ ServiceState ss = ServiceState.newFromBundle(intent.getExtras());
+
+ if (ss != null) {
+ int state = ss.getState();
+ notificationMgr.updateNetworkSelection(state);
+ }
+ }
+
+ public boolean isOtaCallInActiveState() {
+ boolean otaCallActive = false;
+ if (mInCallScreen != null) {
+ otaCallActive = mInCallScreen.isOtaCallInActiveState();
+ }
+ if (VDBG) Log.d(LOG_TAG, "- isOtaCallInActiveState " + otaCallActive);
+ return otaCallActive;
+ }
+
+ public boolean isOtaCallInEndState() {
+ boolean otaCallEnded = false;
+ if (mInCallScreen != null) {
+ otaCallEnded = mInCallScreen.isOtaCallInEndState();
+ }
+ if (VDBG) Log.d(LOG_TAG, "- isOtaCallInEndState " + otaCallEnded);
+ return otaCallEnded;
+ }
+
+ // it is safe to call clearOtaState() even if the InCallScreen isn't active
+ public void clearOtaState() {
+ if (DBG) Log.d(LOG_TAG, "- clearOtaState ...");
+ if ((mInCallScreen != null)
+ && (otaUtils != null)) {
+ otaUtils.cleanOtaScreen(true);
+ if (DBG) Log.d(LOG_TAG, " - clearOtaState clears OTA screen");
+ }
+ }
+
+ // it is safe to call dismissOtaDialogs() even if the InCallScreen isn't active
+ public void dismissOtaDialogs() {
+ if (DBG) Log.d(LOG_TAG, "- dismissOtaDialogs ...");
+ if ((mInCallScreen != null)
+ && (otaUtils != null)) {
+ otaUtils.dismissAllOtaDialogs();
+ if (DBG) Log.d(LOG_TAG, " - dismissOtaDialogs clears OTA dialogs");
+ }
+ }
+
+ // it is safe to call clearInCallScreenMode() even if the InCallScreen isn't active
+ public void clearInCallScreenMode() {
+ if (DBG) Log.d(LOG_TAG, "- clearInCallScreenMode ...");
+ if (mInCallScreen != null) {
+ mInCallScreen.resetInCallScreenMode();
+ }
+ }
+
+ /**
+ * Force the in-call UI to refresh itself, if it's currently visible.
+ *
+ * This method can be used any time there's a state change anywhere in
+ * the phone app that needs to be reflected in the onscreen UI.
+ *
+ * Note that it's *not* necessary to manually refresh the in-call UI
+ * (via this method) for regular telephony state changes like
+ * DIALING -> ALERTING -> ACTIVE, since the InCallScreen already
+ * listens for those state changes itself.
+ *
+ * This method does *not* force the in-call UI to come up if it's not
+ * already visible. To do that, use displayCallScreen().
+ */
+ /* package */ void updateInCallScreen() {
+ if (DBG) Log.d(LOG_TAG, "- updateInCallScreen()...");
+ if (mInCallScreen != null) {
+ // Post an updateScreen() request. Note that the
+ // updateScreen() call will end up being a no-op if the
+ // InCallScreen isn't the foreground activity.
+ mInCallScreen.requestUpdateScreen();
+ }
+ }
+
+ private void handleQueryTTYModeResponse(Message msg) {
+ AsyncResult ar = (AsyncResult) msg.obj;
+ if (ar.exception != null) {
+ if (DBG) Log.d(LOG_TAG, "handleQueryTTYModeResponse: Error getting TTY state.");
+ } else {
+ if (DBG) Log.d(LOG_TAG,
+ "handleQueryTTYModeResponse: TTY enable state successfully queried.");
+
+ int ttymode = ((int[]) ar.result)[0];
+ if (DBG) Log.d(LOG_TAG, "handleQueryTTYModeResponse:ttymode=" + ttymode);
+
+ Intent ttyModeChanged = new Intent(TtyIntent.TTY_ENABLED_CHANGE_ACTION);
+ ttyModeChanged.putExtra("ttyEnabled", ttymode != Phone.TTY_MODE_OFF);
+ sendBroadcastAsUser(ttyModeChanged, UserHandle.ALL);
+
+ String audioTtyMode;
+ switch (ttymode) {
+ case Phone.TTY_MODE_FULL:
+ audioTtyMode = "tty_full";
+ break;
+ case Phone.TTY_MODE_VCO:
+ audioTtyMode = "tty_vco";
+ break;
+ case Phone.TTY_MODE_HCO:
+ audioTtyMode = "tty_hco";
+ break;
+ case Phone.TTY_MODE_OFF:
+ default:
+ audioTtyMode = "tty_off";
+ break;
+ }
+ AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ audioManager.setParameters("tty_mode="+audioTtyMode);
+ }
+ }
+
+ private void handleSetTTYModeResponse(Message msg) {
+ AsyncResult ar = (AsyncResult) msg.obj;
+
+ if (ar.exception != null) {
+ if (DBG) Log.d (LOG_TAG,
+ "handleSetTTYModeResponse: Error setting TTY mode, ar.exception"
+ + ar.exception);
+ }
+ phone.queryTTYMode(mHandler.obtainMessage(EVENT_TTY_MODE_GET));
+ }
+
+ /**
+ * "Call origin" may be used by Contacts app to specify where the phone call comes from.
+ * Currently, the only permitted value for this extra is {@link #ALLOWED_EXTRA_CALL_ORIGIN}.
+ * Any other value will be ignored, to make sure that malicious apps can't trick the in-call
+ * UI into launching some random other app after a call ends.
+ *
+ * TODO: make this more generic. Note that we should let the "origin" specify its package
+ * while we are now assuming it is "com.android.contacts"
+ */
+ public static final String EXTRA_CALL_ORIGIN = "com.android.phone.CALL_ORIGIN";
+ private static final String DEFAULT_CALL_ORIGIN_PACKAGE = "com.android.dialer";
+ private static final String ALLOWED_EXTRA_CALL_ORIGIN =
+ "com.android.dialer.DialtactsActivity";
+ /**
+ * Used to determine if the preserved call origin is fresh enough.
+ */
+ private static final long CALL_ORIGIN_EXPIRATION_MILLIS = 30 * 1000;
+
+ public void setLatestActiveCallOrigin(String callOrigin) {
+ inCallUiState.latestActiveCallOrigin = callOrigin;
+ if (callOrigin != null) {
+ inCallUiState.latestActiveCallOriginTimeStamp = SystemClock.elapsedRealtime();
+ } else {
+ inCallUiState.latestActiveCallOriginTimeStamp = 0;
+ }
+ }
+
+ /**
+ * Reset call origin depending on its timestamp.
+ *
+ * See if the current call origin preserved by the app is fresh enough or not. If it is,
+ * previous call origin will be used as is. If not, call origin will be reset.
+ *
+ * This will be effective especially for 3rd party apps which want to bypass phone calls with
+ * their own telephone lines. In that case Phone app may finish the phone call once and make
+ * another for the external apps, which will drop call origin information in Intent.
+ * Even in that case we are sure the second phone call should be initiated just after the first
+ * phone call, so here we restore it from the previous information iff the second call is done
+ * fairly soon.
+ */
+ public void resetLatestActiveCallOrigin() {
+ final long callOriginTimestamp = inCallUiState.latestActiveCallOriginTimeStamp;
+ final long currentTimestamp = SystemClock.elapsedRealtime();
+ if (VDBG) {
+ Log.d(LOG_TAG, "currentTimeMillis: " + currentTimestamp
+ + ", saved timestamp for call origin: " + callOriginTimestamp);
+ }
+ if (inCallUiState.latestActiveCallOriginTimeStamp > 0
+ && (currentTimestamp - callOriginTimestamp < CALL_ORIGIN_EXPIRATION_MILLIS)) {
+ if (VDBG) {
+ Log.d(LOG_TAG, "Resume previous call origin (" +
+ inCallUiState.latestActiveCallOrigin + ")");
+ }
+ // Do nothing toward call origin itself but update the timestamp just in case.
+ inCallUiState.latestActiveCallOriginTimeStamp = currentTimestamp;
+ } else {
+ if (VDBG) Log.d(LOG_TAG, "Drop previous call origin and set the current one to null");
+ setLatestActiveCallOrigin(null);
+ }
+ }
+
+ /**
+ * @return Intent which will be used when in-call UI is shown and the phone call is hang up.
+ * By default CallLog screen will be introduced, but the destination may change depending on
+ * its latest call origin state.
+ */
+ public Intent createPhoneEndIntentUsingCallOrigin() {
+ if (TextUtils.equals(inCallUiState.latestActiveCallOrigin, ALLOWED_EXTRA_CALL_ORIGIN)) {
+ if (VDBG) Log.d(LOG_TAG, "Valid latestActiveCallOrigin("
+ + inCallUiState.latestActiveCallOrigin + ") was found. "
+ + "Go back to the previous screen.");
+ // Right now we just launch the Activity which launched in-call UI. Note that we're
+ // assuming the origin is from "com.android.dialer", which may be incorrect in the
+ // future.
+ final Intent intent = new Intent();
+ intent.setClassName(DEFAULT_CALL_ORIGIN_PACKAGE, inCallUiState.latestActiveCallOrigin);
+ return intent;
+ } else {
+ if (VDBG) Log.d(LOG_TAG, "Current latestActiveCallOrigin ("
+ + inCallUiState.latestActiveCallOrigin + ") is not valid. "
+ + "Just use CallLog as a default destination.");
+ return PhoneGlobals.createCallLogIntent();
+ }
+ }
+
+ /** Service connection */
+ private final ServiceConnection mBluetoothPhoneConnection = new ServiceConnection() {
+
+ /** Handle the task of binding the local object to the service */
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ Log.i(LOG_TAG, "Headset phone created, binding local service.");
+ mBluetoothPhone = IBluetoothHeadsetPhone.Stub.asInterface(service);
+ }
+
+ /** Handle the task of cleaning up the local binding */
+ public void onServiceDisconnected(ComponentName className) {
+ Log.i(LOG_TAG, "Headset phone disconnected, cleaning local binding.");
+ mBluetoothPhone = null;
+ }
+ };
+}