diff options
Diffstat (limited to 'src/com/android/phone/PhoneGlobals.java')
-rw-r--r-- | src/com/android/phone/PhoneGlobals.java | 1859 |
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; + } + }; +} |