am 448c256d: (-s ours) am 6c91d20e: Fix exported property on NotificationBroadcastReceiver.
* commit '448c256d6c9b86599c7c21e7fb61929704eeed5e':
Fix exported property on NotificationBroadcastReceiver.
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index aabff7b..e9173e5 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -15,6 +15,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
package="com.android.phone"
coreApp="true"
android:sharedUserId="android.uid.phone"
@@ -82,7 +83,9 @@
<uses-permission android:name="android.permission.USE_SIP" />
<uses-permission android:name="android.permission.REBOOT" />
<uses-permission android:name="android.permission.UPDATE_LOCK" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="com.android.smspush.WAPPUSH_MANAGER_BIND" />
+ <uses-permission android:name="android.permission.MANAGE_USERS" />
<!-- This tells the activity manager to not delay any of our activity
start requests, even if they happen immediately after the user
@@ -92,10 +95,11 @@
<application android:name="PhoneApp"
android:persistent="true"
android:label="@string/phoneAppLabel"
- android:icon="@drawable/ic_launcher_phone">
+ android:icon="@mipmap/ic_launcher_phone">
<provider android:name="IccProvider"
android:authorities="icc"
android:multiprocess="true"
+ android:exported="true"
android:readPermission="android.permission.READ_CONTACTS"
android:writePermission="android.permission.WRITE_CONTACTS" />
@@ -141,7 +145,7 @@
<activity android:name="OutgoingCallBroadcaster"
android:theme="@style/OutgoingCallBroadcasterTheme"
android:permission="android.permission.CALL_PHONE"
- android:screenOrientation="portrait"
+ android:screenOrientation="nosensor"
android:configChanges="orientation|screenSize|keyboardHidden">
<!-- CALL action intent filters, for the various ways
of initiating an outgoing call. -->
@@ -198,6 +202,7 @@
<activity-alias android:name="PrivilegedOutgoingCallBroadcaster"
android:targetActivity="OutgoingCallBroadcaster"
+ android:screenOrientation="nosensor"
android:permission="android.permission.CALL_PRIVILEGED">
<intent-filter android:priority="1000">
<action android:name="android.intent.action.CALL_PRIVILEGED" />
@@ -241,8 +246,8 @@
android:label="@string/phoneIconLabel"
android:excludeFromRecents="true"
android:launchMode="singleInstance"
- android:screenOrientation="portrait"
- android:configChanges="orientation|screenSize|keyboardHidden|uiMode"
+ android:screenOrientation="nosensor"
+ android:configChanges="keyboardHidden"
android:exported="false">
</activity>
@@ -435,15 +440,16 @@
</intent-filter>
</activity>
- <!-- bluetooth headset service -->
- <service android:name="BluetoothHeadsetService">
+ <!-- bluetooth phone service -->
+ <service android:name="BluetoothPhoneService">
<intent-filter>
- <action android:name="android.bluetooth.IBluetoothHeadset" />
+ <action android:name="android.bluetooth.IBluetoothHeadsetPhone" />
</intent-filter>
</service>
<!-- Broadcast Receiver that will process BOOT Complete and launch OTA -->
- <receiver android:name="OtaStartupReceiver" android:exported="false">
+ <receiver android:name="OtaStartupReceiver" android:exported="false"
+ androidprv:primaryUserOnly="true">
<intent-filter android:priority="100">
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
@@ -495,7 +501,7 @@
</activity>
<activity android:name=".SipCallOptionHandler"
android:theme="@style/SipCallOptionHandlerTheme"
- android:screenOrientation="portrait"
+ android:screenOrientation="nosensor"
android:configChanges="orientation|screenSize|keyboardHidden">
</activity>
@@ -515,7 +521,7 @@
</receiver>
<!-- BroadcastReceiver for receiving Intents from Notification mechanism. -->
- <receiver android:name="PhoneApp$NotificationBroadcastReceiver" android:exported="false">
+ <receiver android:name="PhoneGlobals$NotificationBroadcastReceiver" android:exported="false">
<intent-filter>
<action android:name="com.android.phone.ACTION_HANG_UP_ONGOING_CALL" />
<action android:name="com.android.phone.ACTION_CALL_BACK_FROM_NOTIFICATION" />
diff --git a/res/drawable-hdpi/ic_launcher_phone.png b/res/drawable-hdpi/ic_launcher_phone.png
deleted file mode 100644
index 5a3dff1..0000000
--- a/res/drawable-hdpi/ic_launcher_phone.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_phone.png b/res/drawable-mdpi/ic_launcher_phone.png
deleted file mode 100644
index 9ea0d8c..0000000
--- a/res/drawable-mdpi/ic_launcher_phone.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-nodpi/background_dial_holo_dark.png b/res/drawable-nodpi/background_dial_holo_dark.png
new file mode 100644
index 0000000..3dba50c
--- /dev/null
+++ b/res/drawable-nodpi/background_dial_holo_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher_phone.png b/res/drawable-xhdpi/ic_launcher_phone.png
deleted file mode 100644
index e97836c..0000000
--- a/res/drawable-xhdpi/ic_launcher_phone.png
+++ /dev/null
Binary files differ
diff --git a/res/layout-land/incall_screen.xml b/res/layout-land/incall_screen.xml
new file mode 100644
index 0000000..1278115
--- /dev/null
+++ b/res/layout-land/incall_screen.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+
+<!-- In-call Phone UI; see InCallScreen.java. -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/landscape_incall_screen"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <!-- The "Call Card", which displays info about the currently
+ active phone call(s) on the device. See call_card.xml. -->
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/background_dial_holo_dark">
+ <include
+ layout="@layout/call_card"
+ android:id="@+id/callCard"
+ android:layout_width="0dp"
+ android:layout_weight="3"
+ android:layout_height="match_parent" />
+ <!-- Note: This center margin is repeated in layout-land/incall_touch_ui
+ Both layouts need to have this margin to be aligned correctly. -->
+ <View
+ android:layout_width="@dimen/dialpad_center_margin"
+ android:layout_weight="0"
+ android:layout_height="match_parent" />
+ <View
+ android:layout_width="0dp"
+ android:layout_weight="2"
+ android:layout_height="match_parent" />
+ </LinearLayout>
+
+ <!-- In-call onscreen touch controls; see InCallTouchUi.java.
+ This widget contains the cluster of buttons shown at the right
+ of the in-call screen, and also the DTMF dialpad (which, when
+ visible, covers the contact picture/call_card on the left half of the screen) -->
+
+ <ViewStub
+ android:id="@+id/inCallTouchUiStub"
+ android:layout="@layout/incall_touch_ui"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+ <ViewStub
+ android:id="@+id/inCallTouchUiCdmaStub"
+ android:layout="@layout/incall_touch_ui_cdma"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <!-- ViewStub for OTASP-related UI elements (for the CDMA "activation"
+ call.) Note that this ViewStub provides the *entire* OTASP
+ screen, including the status area at the top *and* touch controls
+ at the bottom of the screen. The regular CallCard and the
+ InCallTouchUi widget are not used at all during an OTASP call. -->
+ <ViewStub android:id="@+id/otaCallCardStub"
+ android:layout="@layout/otacall_card"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <!-- The "Manage conference" UI. This panel is displayed (and covers up
+ the entire normal in-call UI) when the user clicks "Manage conference"
+ during a GSM conference call. -->
+ <ViewStub android:id="@+id/manageConferencePanelStub"
+ android:layout="@layout/manage_conference_panel"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+</FrameLayout>
diff --git a/res/layout-land/incall_touch_ui.xml b/res/layout-land/incall_touch_ui.xml
new file mode 100644
index 0000000..7ab56a1
--- /dev/null
+++ b/res/layout-land/incall_touch_ui.xml
@@ -0,0 +1,251 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+
+<!-- In-call onscreen touch UI elements, used on some platforms.
+
+ This layout is a fullscreen overlay, drawn on top of the
+ non-touch-sensitive parts of the in-call UI (i.e. the call card).
+
+ The top-level View here is a InCallTouchUi (FrameLayout) with 2 children:
+ (1) inCallControls: the widgets visible while a regular call (or calls) is in progress
+ (2) incomingCallWidget: the UI displayed while an incoming call is ringing
+ In usual cases, one of these is visible at any given moment.
+ One exception is when incomingCallWidget is fading-out. At that moment, we show
+ inCallControls beneath incomingCallWidget for smoother transition.
+ -->
+<com.android.phone.InCallTouchUi xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:prvandroid="http://schemas.android.com/apk/prv/res/android"
+ android:id="@+id/inCallTouchUi"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <!--
+ (1) inCallControls: the widgets visible while a regular call
+ (or calls) is in progress
+ -->
+ <LinearLayout
+ android:id="@+id/inCallControls"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone">
+
+ <!-- DTMF dialpad shown in the left part of the screen.
+ This is wrapped in a FrameLayout because otherwise the ViewStub has no width,
+ causing the other buttons to span the full width of the screen -->
+ <FrameLayout
+ android:layout_width="0dp"
+ android:layout_weight="3"
+ android:layout_height="match_parent">
+ <ViewStub
+ android:id="@+id/dtmf_twelve_key_dialer_stub"
+ android:layout="@layout/dtmf_twelve_key_dialer_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <!-- The "extra button row": Rare button used for statues such as conference
+ calls (GSM). Refer to layout(portrait)/incall_touch_ui for more details.
+ When shown, this extra button row hovers over the call card, just above
+ the primary_call_banner of layout-land/primary_call_info. -->
+ <ViewStub
+ android:id="@+id/extraButtonRow"
+ android:layout_gravity="bottom"
+ android:layout_marginBottom="@dimen/call_banner_height"
+ android:layout="@layout/extra_button_row"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </FrameLayout>
+
+ <!-- Note: This center margin is repeated in layout-land/incall_screen -->
+ <View
+ android:layout_width="@dimen/dialpad_center_margin"
+ android:layout_height="match_parent"
+ android:background="#66000000" />
+
+ <!-- Cluster of buttons on the right part of the screen.
+ It is named id/bottomButtons from the naming when in portrait layout. -->
+ <LinearLayout
+ android:id="@+id/bottomButtons"
+ android:background="@drawable/dialpad_background"
+ android:orientation="vertical"
+ android:layout_width="0dp"
+ android:layout_weight="2"
+ android:layout_height="match_parent">
+
+ <!-- "Audio mode" -->
+ <!-- This is a multi-mode button that can behave either like a
+ simple "compound button" with two states *or* like an
+ action button that brings up a popup menu; see
+ btn_compound_audio.xml and InCallTouchUi.updateAudioButton()
+ for the full details. -->
+ <ToggleButton
+ android:id="@+id/audioButton"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:layout_width="match_parent"
+ style="@style/InCallCompoundButton"
+ android:background="@drawable/btn_compound_audio"
+ android:contentDescription="@string/onscreenAudioText" />
+
+ <View
+ android:layout_height="@dimen/dialpad_button_margin"
+ android:layout_width="match_parent"
+ android:background="#66000000" />
+
+ <LinearLayout
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:layout_width="match_parent">
+ <ToggleButton
+ android:id="@+id/muteButton"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ style="@style/InCallCompoundButton"
+ android:background="@drawable/btn_compound_mute"
+ android:contentDescription="@string/onscreenMuteText" />
+ <View
+ android:layout_width="@dimen/dialpad_button_margin"
+ android:layout_height="match_parent"
+ android:background="#66000000" />
+ <!-- This slot is either "Hold" or "Swap", depending on
+ the state of the call. One or the other of these
+ must always be set to GONE. -->
+ <!-- "Hold" -->
+ <!-- This is a "compound button": it has checked and unchecked states. -->
+ <ToggleButton
+ android:id="@+id/holdButton"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ style="@style/InCallCompoundButton"
+ android:background="@drawable/btn_compound_hold"
+ android:contentDescription="@string/onscreenHoldText" />
+ <!-- "Swap" (or "Manage calls" in some CDMA states) -->
+ <ImageButton
+ android:id="@+id/swapButton"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ style="@style/InCallButton"
+ android:src="@drawable/ic_incall_switch_holo_dark"
+ android:contentDescription="@string/onscreenSwapCallsText" />
+ </LinearLayout>
+
+ <View
+ android:layout_height="@dimen/dialpad_button_margin"
+ android:layout_width="match_parent"
+ android:background="#66000000" />
+
+ <LinearLayout
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:layout_width="match_parent">
+ <ToggleButton
+ android:id="@+id/dialpadButton"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ style="@style/InCallCompoundButton"
+ android:background="@drawable/btn_compound_dialpad"
+ android:contentDescription="@string/onscreenShowDialpadText" />
+ <View
+ android:layout_width="@dimen/dialpad_button_margin"
+ android:layout_height="match_parent"
+ android:background="#66000000" />
+ <!-- This slot is either "Add" or "Merge", depending on
+ the state of the call. One or the other of these
+ must always be set to GONE. -->
+ <!-- "Add Call" -->
+ <ImageButton
+ android:id="@+id/addButton"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ style="@style/InCallButton"
+ android:src="@drawable/ic_add_contact_holo_dark"
+ android:contentDescription="@string/onscreenAddCallText" />
+ <!-- "Merge calls" -->
+ <!-- This button is used only on GSM devices, where we know
+ that "Add" and "Merge" are never available at the same time.
+ The "Merge" button for CDMA devices is "cdmaMergeButton" above. -->
+ <ImageButton
+ android:id="@+id/mergeButton"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ style="@style/InCallButton"
+ android:src="@drawable/ic_merge_holo_dark"
+ android:contentDescription="@string/onscreenMergeCallsText" />
+ </LinearLayout>
+
+ <!-- This spacer is not used in GSM, so it has 0 width and height. The CDMA
+ incall_touch_ui_cdma uses it as a spacer when a 5th button is shown. -->
+ <View
+ android:id="@+id/holdSwapSpacer"
+ android:layout_width="0dp"
+ android:layout_height="0dp" />
+ <View
+ android:layout_height="@dimen/dialpad_button_margin"
+ android:layout_width="match_parent"
+ android:background="#66000000" />
+ <ImageButton
+ android:id="@+id/endButton"
+ android:layout_height="@dimen/in_call_end_button_height"
+ android:layout_width="match_parent"
+ android:src="@drawable/ic_dial_end_call"
+ android:background="@drawable/end_call_background"
+ android:contentDescription="@string/onscreenEndCallText" />
+ </LinearLayout>
+ </LinearLayout>
+
+ <!--
+ (2) incomingCallWidget: the UI displayed while an incoming call is ringing.
+ See InCallTouchUi.showIncomingCallWidget().
+
+ Layout notes:
+ - Use an opaque black background since we need to cover up
+ a bit of the bottom of the contact photo
+ - The verticalOffset value gets us a little extra space above
+ the topmost "Respond by SMS" icon
+ - The negative layout_marginBottom shifts us slightly downward;
+ we're already aligned with the bottom of the screen, but we
+ don't have an icon in the downward direction so the whole
+ bottom area of this widget is just wasted space.
+ -->
+ <com.android.internal.widget.multiwaveview.GlowPadView
+ android:id="@+id/incomingCallWidget"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="right"
+ android:background="@android:color/black"
+ android:visibility="gone"
+ android:gravity="top"
+
+ prvandroid:targetDrawables="@array/incoming_call_widget_3way_targets"
+ prvandroid:targetDescriptions="@array/incoming_call_widget_3way_target_descriptions"
+ prvandroid:directionDescriptions="@array/incoming_call_widget_3way_direction_descriptions"
+ prvandroid:handleDrawable="@drawable/ic_in_call_touch_handle"
+ prvandroid:innerRadius="@*android:dimen/glowpadview_inner_radius"
+ prvandroid:outerRadius="@*android:dimen/glowpadview_target_placement_radius"
+ prvandroid:outerRingDrawable="@*android:drawable/ic_lockscreen_outerring"
+ prvandroid:snapMargin="@*android:dimen/glowpadview_snap_margin"
+ prvandroid:vibrationDuration="20"
+ prvandroid:feedbackCount="1"
+ prvandroid:glowRadius="@*android:dimen/glowpadview_glow_radius"
+ prvandroid:pointDrawable="@*android:drawable/ic_lockscreen_glowdot"
+ />
+
+</com.android.phone.InCallTouchUi>
diff --git a/res/layout-land/incall_touch_ui_cdma.xml b/res/layout-land/incall_touch_ui_cdma.xml
new file mode 100644
index 0000000..ce96612
--- /dev/null
+++ b/res/layout-land/incall_touch_ui_cdma.xml
@@ -0,0 +1,248 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+
+<!-- In-call onscreen touch UI elements, used on some platforms.
+
+ This layout is a fullscreen overlay, drawn on top of the
+ non-touch-sensitive parts of the in-call UI (i.e. the call card).
+
+ The top-level View here is a InCallTouchUi (FrameLayout) with 2 children:
+ (1) inCallControls: the widgets visible while a regular call (or calls) is in progress
+ (2) incomingCallWidget: the UI displayed while an incoming call is ringing
+ In usual cases, one of these is visible at any given moment.
+ One exception is when incomingCallWidget is fading-out. At that moment, we show
+ inCallControls beneath incomingCallWidget for smoother transition.
+ -->
+<com.android.phone.InCallTouchUi xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:prvandroid="http://schemas.android.com/apk/prv/res/android"
+ android:id="@+id/inCallTouchUi"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <!--
+ (1) inCallControls: the widgets visible while a regular call
+ (or calls) is in progress
+ -->
+ <LinearLayout
+ android:id="@+id/inCallControls"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone">
+
+ <!-- DTMF dialpad shown in the left part of the screen.
+ This is wrapped in a FrameLayout because otherwise the ViewStub has no width,
+ causing the other buttons to span the full width of the screen -->
+ <FrameLayout
+ android:layout_width="0dp"
+ android:layout_weight="3"
+ android:layout_height="match_parent">
+ <ViewStub
+ android:id="@+id/dtmf_twelve_key_dialer_stub"
+ android:layout="@layout/dtmf_twelve_key_dialer_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <!-- The "extra button row": Rare button used for statues such as conference
+ calls (GSM). Refer to layout(portrait)/incall_touch_ui for more details.
+ When shown, this extra button row hovers over the call card, just above
+ the primary_call_banner of layout-land/primary_call_info.
+ I believe, since this is the CDMA layout, that the button might not be shown. -->
+ <ViewStub
+ android:id="@+id/extraButtonRow"
+ android:layout_gravity="bottom"
+ android:layout_marginBottom="@dimen/call_banner_height"
+ android:layout="@layout/extra_button_row"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </FrameLayout>
+
+ <!-- Note: This center margin is repeated in layout-land/incall_screen -->
+ <View
+ android:layout_width="@dimen/dialpad_center_margin"
+ android:layout_height="match_parent"
+ android:background="#66000000" />
+
+ <!-- Cluster of buttons on the right part of the screen.
+ It is named id/bottomButtons from the naming when in portrait layout. -->
+ <LinearLayout
+ android:id="@+id/bottomButtons"
+ android:background="@drawable/dialpad_background"
+ android:orientation="vertical"
+ android:layout_width="0dp"
+ android:layout_weight="2"
+ android:layout_height="match_parent">
+
+ <!-- This slot is either "Hold" or "Swap", depending on
+ the state of the call. One or the other of these
+ must always be set to GONE. -->
+ <!-- "Hold" -->
+ <!-- This is a "compound button": it has checked and unchecked states. -->
+ <ToggleButton
+ android:id="@+id/holdButton"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:layout_width="match_parent"
+ style="@style/InCallCompoundButton"
+ android:background="@drawable/btn_compound_hold"
+ android:contentDescription="@string/onscreenHoldText" />
+ <!-- "Swap" (or "Manage calls" in some CDMA states) -->
+ <ImageButton
+ android:id="@+id/swapButton"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:layout_width="match_parent"
+ style="@style/InCallButton"
+ android:src="@drawable/ic_incall_switch_holo_dark"
+ android:contentDescription="@string/onscreenSwapCallsText" />
+
+
+ <View
+ android:id="@+id/holdSwapSpacer"
+ android:layout_height="@dimen/dialpad_button_margin"
+ android:layout_width="match_parent"
+ android:background="#66000000" />
+
+ <LinearLayout
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:layout_width="match_parent">
+ <ToggleButton
+ android:id="@+id/muteButton"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ style="@style/InCallCompoundButton"
+ android:background="@drawable/btn_compound_mute"
+ android:contentDescription="@string/onscreenMuteText" />
+ <View
+ android:layout_width="@dimen/dialpad_button_margin"
+ android:layout_height="match_parent"
+ android:background="#66000000" />
+ <!-- "Audio mode" -->
+ <!-- This is a multi-mode button that can behave either like a
+ simple "compound button" with two states *or* like an
+ action button that brings up a popup menu; see
+ btn_compound_audio.xml and InCallTouchUi.updateAudioButton()
+ for the full details. -->
+ <ToggleButton
+ android:id="@+id/audioButton"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ style="@style/InCallCompoundButton"
+ android:background="@drawable/btn_compound_audio"
+ android:contentDescription="@string/onscreenAudioText" />
+ </LinearLayout>
+
+ <View
+ android:layout_height="@dimen/dialpad_button_margin"
+ android:layout_width="match_parent"
+ android:background="#66000000" />
+
+ <LinearLayout
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:layout_width="match_parent">
+ <ToggleButton
+ android:id="@+id/dialpadButton"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ style="@style/InCallCompoundButton"
+ android:background="@drawable/btn_compound_dialpad"
+ android:contentDescription="@string/onscreenShowDialpadText" />
+ <View
+ android:layout_width="@dimen/dialpad_button_margin"
+ android:layout_height="match_parent"
+ android:background="#66000000" />
+ <!-- This slot is either "Add" or "Merge", depending on
+ the state of the call. One or the other of these
+ must always be set to GONE. -->
+ <!-- "Add Call" -->
+ <ImageButton
+ android:id="@+id/addButton"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ style="@style/InCallButton"
+ android:src="@drawable/ic_add_contact_holo_dark"
+ android:contentDescription="@string/onscreenAddCallText" />
+ <!-- "Merge calls" -->
+ <!-- This button is used only on GSM devices, where we know
+ that "Add" and "Merge" are never available at the same time.
+ The "Merge" button for CDMA devices is "cdmaMergeButton" above. -->
+ <ImageButton
+ android:id="@+id/mergeButton"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ style="@style/InCallButton"
+ android:src="@drawable/ic_merge_holo_dark"
+ android:contentDescription="@string/onscreenMergeCallsText" />
+ </LinearLayout>
+
+ <View
+ android:layout_height="@dimen/dialpad_button_margin"
+ android:layout_width="match_parent"
+ android:background="#66000000" />
+ <ImageButton
+ android:id="@+id/endButton"
+ android:layout_height="@dimen/in_call_end_button_height"
+ android:layout_width="match_parent"
+ android:src="@drawable/ic_dial_end_call"
+ android:background="@drawable/end_call_background"
+ android:contentDescription="@string/onscreenEndCallText" />
+ </LinearLayout>
+ </LinearLayout>
+
+ <!--
+ (2) incomingCallWidget: the UI displayed while an incoming call is ringing.
+ See InCallTouchUi.showIncomingCallWidget().
+
+ Layout notes:
+ - Use an opaque black background since we need to cover up
+ a bit of the bottom of the contact photo
+ - The verticalOffset value gets us a little extra space above
+ the topmost "Respond by SMS" icon
+ - The negative layout_marginBottom shifts us slightly downward;
+ we're already aligned with the bottom of the screen, but we
+ don't have an icon in the downward direction so the whole
+ bottom area of this widget is just wasted space.
+ -->
+ <com.android.internal.widget.multiwaveview.GlowPadView
+ android:id="@+id/incomingCallWidget"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="right"
+ android:background="@android:color/black"
+ android:visibility="gone"
+ android:gravity="top"
+
+ prvandroid:targetDrawables="@array/incoming_call_widget_3way_targets"
+ prvandroid:targetDescriptions="@array/incoming_call_widget_3way_target_descriptions"
+ prvandroid:directionDescriptions="@array/incoming_call_widget_3way_direction_descriptions"
+ prvandroid:handleDrawable="@drawable/ic_in_call_touch_handle"
+ prvandroid:innerRadius="@*android:dimen/glowpadview_inner_radius"
+ prvandroid:outerRadius="@*android:dimen/glowpadview_target_placement_radius"
+ prvandroid:outerRingDrawable="@*android:drawable/ic_lockscreen_outerring"
+ prvandroid:snapMargin="@*android:dimen/glowpadview_snap_margin"
+ prvandroid:vibrationDuration="20"
+ prvandroid:feedbackCount="1"
+ prvandroid:glowRadius="@*android:dimen/glowpadview_glow_radius"
+ prvandroid:pointDrawable="@*android:drawable/ic_lockscreen_glowdot"
+ />
+
+</com.android.phone.InCallTouchUi>
diff --git a/res/layout/incall_screen.xml b/res/layout/incall_screen.xml
index da9a509..64df29a 100644
--- a/res/layout/incall_screen.xml
+++ b/res/layout/incall_screen.xml
@@ -38,7 +38,6 @@
of the in-call screen, and also the DTMF dialpad (which, when
visible, covers the upper part of the screen too.) -->
<include layout="@layout/incall_touch_ui"
- android:id="@+id/inCallTouchUi"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
diff --git a/res/layout/incall_touch_ui.xml b/res/layout/incall_touch_ui.xml
index c070782..8dd8b61 100644
--- a/res/layout/incall_touch_ui.xml
+++ b/res/layout/incall_touch_ui.xml
@@ -28,6 +28,7 @@
-->
<com.android.phone.InCallTouchUi xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:prvandroid="http://schemas.android.com/apk/prv/res/android"
+ android:id="@+id/inCallTouchUi"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
diff --git a/res/layout/manage_conference_panel.xml b/res/layout/manage_conference_panel.xml
index 7baf09b..f1fbfdf 100644
--- a/res/layout/manage_conference_panel.xml
+++ b/res/layout/manage_conference_panel.xml
@@ -19,6 +19,7 @@
button while on a conference call. -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/manageConferencePanel"
+ android:background="#FF000000"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
diff --git a/res/layout/primary_call_info.xml b/res/layout/primary_call_info.xml
index 73718ee..099f19e 100644
--- a/res/layout/primary_call_info.xml
+++ b/res/layout/primary_call_info.xml
@@ -68,7 +68,7 @@
and other status info. This info is shown as a "banner"
overlaid across the top of contact photo. -->
<RelativeLayout android:id="@+id/primary_call_banner"
- android:layout_alignParentTop="true"
+ style="@style/PrimaryCallInfoPrimaryCallBanner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/call_banner_height"
@@ -137,7 +137,7 @@
</RelativeLayout> <!-- End of call_banner -->
<LinearLayout android:id="@+id/secondary_info_container"
- android:layout_below="@id/primary_call_banner"
+ style="@style/PrimaryCallInfoSecondaryInfoContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|right"
diff --git a/res/layout/secondary_call_info.xml b/res/layout/secondary_call_info.xml
index 0e6e65f..516b615 100644
--- a/res/layout/secondary_call_info.xml
+++ b/res/layout/secondary_call_info.xml
@@ -25,8 +25,6 @@
<!-- Contact photo for call_info #2 -->
<ImageView android:id="@+id/secondaryCallPhoto"
- android:layout_alignParentLeft="true"
- android:layout_alignParentTop="true"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="top|center_horizontal"
@@ -37,8 +35,6 @@
TODO: We could do better. See equivalent in primary xml. -->
<View android:id="@+id/dim_effect_for_secondary_photo"
- android:layout_alignParentLeft="true"
- android:layout_alignParentTop="true"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/clickable_dim_effect"
@@ -46,6 +42,7 @@
<!-- Name (or the phone number, if we don't have a name to display). -->
<TextView android:id="@+id/secondaryCallName"
+ style="@style/SecondaryCallInfoSecondaryCallName"
android:layout_width="match_parent"
android:layout_height="@dimen/call_banner_height"
android:gravity="top|left"
@@ -60,9 +57,9 @@
<!-- Call status of the background call, usually the string "On hold". -->
<TextView android:id="@+id/secondaryCallStatus"
+ style="@style/SecondaryCallInfoSecondaryCallStatus"
android:layout_width="wrap_content"
android:layout_height="@dimen/call_banner_height"
- android:layout_gravity="top|right"
android:gravity="top|right"
android:paddingLeft="@dimen/call_banner_side_padding"
android:paddingRight="@dimen/call_banner_side_padding"
diff --git a/res/mipmap-hdpi/ic_launcher_phone.png b/res/mipmap-hdpi/ic_launcher_phone.png
new file mode 100644
index 0000000..47d7894
--- /dev/null
+++ b/res/mipmap-hdpi/ic_launcher_phone.png
Binary files differ
diff --git a/res/mipmap-mdpi/ic_launcher_phone.png b/res/mipmap-mdpi/ic_launcher_phone.png
new file mode 100644
index 0000000..3b333cf
--- /dev/null
+++ b/res/mipmap-mdpi/ic_launcher_phone.png
Binary files differ
diff --git a/res/mipmap-xhdpi/ic_launcher_phone.png b/res/mipmap-xhdpi/ic_launcher_phone.png
new file mode 100644
index 0000000..020c2fa
--- /dev/null
+++ b/res/mipmap-xhdpi/ic_launcher_phone.png
Binary files differ
diff --git a/res/mipmap-xxhdpi/ic_launcher_contacts.png b/res/mipmap-xxhdpi/ic_launcher_contacts.png
new file mode 100644
index 0000000..99b403b
--- /dev/null
+++ b/res/mipmap-xxhdpi/ic_launcher_contacts.png
Binary files differ
diff --git a/res/mipmap-xxhdpi/ic_launcher_phone.png b/res/mipmap-xxhdpi/ic_launcher_phone.png
new file mode 100644
index 0000000..1594e4e
--- /dev/null
+++ b/res/mipmap-xxhdpi/ic_launcher_phone.png
Binary files differ
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 0bfd714..ef82556 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -399,6 +399,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"Lui af"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"In oproep"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"My nommer is <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Bel"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Gemiste oproep"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Gemiste oproepe"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> gemiste oproepe"</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index a0da9d3..f068ba4 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"በመዝጋት ላይ"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"ጥሪ ላይ"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"ቁጥሬ<xliff:g id="MY_PHONE_NUMBER">%s</xliff:g> ነው"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"በመደወል ላይ"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"ያመለጠጥሪ"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"ያመለጡ ጥሪዎች"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> ያመለጡ ጥሪዎች"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 0990b24..e04a955 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"إنهاء المكالمة"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"قيد الاتصال"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"رقمي هو <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"جارٍ الطلب"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"مكالمة فائتة"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"المكالمات الفائتة"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> من المكالمات الفائتة"</string>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 5b1d820..9cf2c0a 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"Завяршэнне"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"У выкліку"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"Мой нумар – <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Набор"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Прапушчаны выклік"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Прапушчаныя выклікі"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"Прапушчаных выклікаў: <xliff:g id="NUM_MISSED_CALLS">%s</xliff:g>"</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index e859348..79f37ae 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"Приключване на разговора"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"Вх. обаждане"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"Моят номер е <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Набира се"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Пропуснато обаждане"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Пропуснати обаждания"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> пропуснати обаждания"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 072b610..6a9aa2d 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"S\'està penjant"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"En una trucada"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"El meu número és <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Marcatge"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Trucada perduda"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Trucades perdudes"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> trucades perdudes"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 587ea69..af013e2 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"Zavěšování"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"Probíhá hovor"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"Moje číslo je <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Vytáčení"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Zmeškaný hovor"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Zmeškané hovory"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"Zmeškané hovory: <xliff:g id="NUM_MISSED_CALLS">%s</xliff:g>."</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 8cbbd12..ac2441b 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -401,6 +401,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"Lægger på"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"Opkald i gang"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"Mit nummer er <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Ringer op"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Ubesvarede opkald"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Ubesvarede opkald"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> ubesvarede opkald"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index b638884..74e8596 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -399,6 +399,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"Auflegen"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"Anruf"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"Meine Nummer lautet <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Verbindung wird aufgebaut..."</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Entgangener Anruf"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Entgangene Anrufe"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> entgangene Anrufe"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 78679e1..6bf050b 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"Κλείσιμο γραμμής"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"Σε κλήση"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"Ο αριθμός μου είναι <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Κλήση"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Αναπάντητη κλήση"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Αναπάντητες κλήσεις"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> αναπάντητες κλήσεις"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index ac52314..27a116d 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"Hanging up"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"In call"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"My number is <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Dialling"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Missed call"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Missed calls"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> missed calls"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 6db20ee..316e086 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"Colgando"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"En llamada"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"Mi número es <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Marcando"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Llamada perdida"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Llamadas perdidas"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> llamadas perdidas"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index bd6946d..21fb011 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -399,6 +399,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"Colgando"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"Llamada entrante"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"Mi número es <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Llamando..."</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Llamada perdida"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Llamadas perdidas"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> llamadas perdidas"</string>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 7f934d0..1755314 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"Lõpetamisel"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"Kõne"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"Minu number on <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Valimine"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Vastamata kõne"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Vastamata kõned"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> vastamata kõnet"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 1163ebc..26da88e 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"قطع تماس"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"در تماس"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"شماره من <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g> است"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"شمارهگیری"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"تماس بی پاسخ"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"تماسهای بی پاسخ"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> تماس بی پاسخ"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 1587076..be763c4 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -399,6 +399,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"Katkaistaan"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"Puhelun aikana"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"Numeroni on <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Soitetaan"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Vastaamatta jäänyt puhelu"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Vastaamattomat puhelut"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> vastaamatonta puhelua"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 953a44e..4f96455 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"Fin de l\'appel.."</string>
<string name="card_title_in_call" msgid="6346543933068225205">"Appel en cours"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"Mon numéro est le <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Numérotation en cours…"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Appel manqué"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Appels manqués"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> appels manqués"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 0880230..86de7d3 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"बंद कर रहा है"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"कॉल में"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"मेरा नंबर <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g> है"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"डायल हो रहा है"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"छूटी कॉल"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"छूटी कॉल"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> छूटी कॉल"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index eda2ac7..e3f6d31 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"Prekidanje veze"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"Poziv u tijeku"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"Moj broj je <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Biranje broja"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Propušteni poziv"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Propušteni pozivi"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"Broj propuštenih poziva: <xliff:g id="NUM_MISSED_CALLS">%s</xliff:g>"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 4319e01..261dba2 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"Megszakítás"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"Hívásban"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"A számom: <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Tárcsázás"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Nem fogadott hívás"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Nem fogadott hívások"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> nem fogadott hívás"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index e0c4af8..b660a3e 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"Menutup panggilan"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"Sedang dalam panggilan"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"Nomor saya <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Memanggil"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Panggilan tak terjawab"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Panggilan tak terjawab"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> panggilan tak terjawab"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index c792049..442aceb 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -399,6 +399,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"In fase di chiusura"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"Chiamata"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"Il mio numero è <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Chiamata in corso"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Chiamata senza risposta"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Chiamate senza risposta"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> chiamate senza risposta"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 3bf8f95..193ebe6 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"מנתק"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"בשיחה"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"המספר שלי הוא <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"מחייג"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"שיחה שלא נענתה"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"שיחות שלא נענו"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> שיחות שלא נענו"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index fd97dcf..daf0844 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"通話終了"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"着信"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"この電話の番号: <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"発信中"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"不在着信"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"不在着信"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"不在着信<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g>件"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 9ed70a3..982b863 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"전화 끊는 중"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"통화 상태"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"내 전화번호는 <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>입니다."</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"전화 거는 중"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"부재중 전화"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"부재중 통화"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"부재중 통화 <xliff:g id="NUM_MISSED_CALLS">%s</xliff:g>통"</string>
diff --git a/res/values-land/dimens.xml b/res/values-land/dimens.xml
index 56ad159..9eb531d 100644
--- a/res/values-land/dimens.xml
+++ b/res/values-land/dimens.xml
@@ -15,4 +15,7 @@
-->
<resources>
<dimen name="emergency_dialer_digits_height">66dip</dimen>
+
+ <!-- Height of the "call banner" overlay on bottom of the call_card -->
+ <dimen name="call_banner_height">@dimen/in_call_end_button_height</dimen>
</resources>
diff --git a/res/values-land/styles.xml b/res/values-land/styles.xml
new file mode 100644
index 0000000..0626a15
--- /dev/null
+++ b/res/values-land/styles.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+
+<resources>
+ <style name="PrimaryCallInfoPrimaryCallBanner">
+ <item name="android:layout_alignParentBottom">true</item>
+ </style>
+ <style name="PrimaryCallInfoSecondaryInfoContainer">
+ <item name="android:layout_above">@id/primary_call_banner</item>
+ </style>
+
+ <style name="SecondaryCallInfoSecondaryCallName">
+ <item name="android:layout_gravity">bottom|left</item>
+ </style>
+ <style name="SecondaryCallInfoSecondaryCallStatus">
+ <item name="android:layout_gravity">bottom|right</item>
+ </style>
+</resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 67c7e61..b4d8269 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"Baigiamas pokalbis"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"Skambinant"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"Mano numeris: <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Renkamas numeris"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Praleistas skambutis"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Praleisti skambučiai"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> praleisti (-ų) skambučiai (-ų)"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index c2c8594..9893974 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -397,6 +397,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"Notiek klausules nolikšana"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"Notiek saruna"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"Mans tālruņa numurs: <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Notiek numura sastādīšana"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Neatbildēts zvans"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Neatbildētie zvani"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> neatbildēts(-i) zvans(-i)"</string>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index 7cdb49f..92536c2 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"Menamatkan panggilan"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"Dalam panggilan"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"Nombor saya ialah <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Mendail"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Panggilan tidak dijawab"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Panggilan tidak dijawab"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> panggilan tidak dijawab"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index a706730..4cd6ee4 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -401,6 +401,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"Legger på"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"Samtale pågår"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"Nummeret mitt er <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Slår nummeret"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Tapt anrop"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Tapte anrop"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> tapte anrop"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 3b981dc..efe72ba 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"Ophangen"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"Actieve oproep"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"Mijn nummer is <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Kiezen"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Gemiste oproep"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Gemiste oproepen"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> gemiste oproepen"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 0a65331..15deb2e 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"Trwa rozłączanie"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"Trwa połączenie"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"Mój numer to <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Trwa wybieranie numeru"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Nieodebrane połączenie"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Połączenia nieodebrane"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> nieodebranych połączeń"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index e9a16ec..34fdc61 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"A desligar"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"A chamar"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"O meu número é <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"A marcar"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Chamada não atendida"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Chamadas não atendidas"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> chamadas não atendidas"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 05dc83f..177ba64 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"Desligando"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"Em chamada"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"Meu número é <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Discando"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Chamada perdida"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Chamadas perdidas"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> chamadas perdidas"</string>
diff --git a/res/values-rm/strings.xml b/res/values-rm/strings.xml
index 422022d..31002f9 100644
--- a/res/values-rm/strings.xml
+++ b/res/values-rm/strings.xml
@@ -463,6 +463,8 @@
<string name="card_title_in_call" msgid="6346543933068225205">"Clom"</string>
<!-- no translation found for card_title_my_phone_number (112428362494434191) -->
<skip />
+ <!-- no translation found for notification_dialing (2107666444937350731) -->
+ <skip />
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Clom manchentà"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Cloms manchentads"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> cloms manchentads"</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 6840427..3611653 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"Se închide telefonul"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"În timpul apelului"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"Numărul meu este <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Apelează"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Apel nepreluat"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Apeluri nepreluate"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> (de) apeluri nepreluate"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 8909248..ef165ee 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -397,6 +397,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"Завершение разговора"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"Поступающий вызов"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"Мой номер: <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Набор номера"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Пропущенный вызов"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Пропущенные вызовы"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"Пропущенных вызовов: <xliff:g id="NUM_MISSED_CALLS">%s</xliff:g>"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 112e3bb..080a0b3 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"Ukončovanie hovoru"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"Prebieha hovor"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"Moje číslo je <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Vytáčanie"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Zmeškaný hovor"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Zmeškané hovory"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"Zmeškané hovory: <xliff:g id="NUM_MISSED_CALLS">%s</xliff:g>."</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 368b43e..057993d 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"Prekinitev"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"Dohodni klic"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"Moja številka je <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Klicanje"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Neodgovorjeni klic"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Neodgovorjeni klici"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> neodgovorjenih klicev"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index a5a0f2c..6c47923 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"Прекид везе"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"Има позив"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"Мој број је <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Бирање"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Пропуштен позив"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Пропуштени позиви"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"Број пропуштених позива: <xliff:g id="NUM_MISSED_CALLS">%s</xliff:g>"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 1bc649e..e8366a4 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -401,6 +401,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"Lägger på"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"I samtal"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"Mitt nummer är <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Ringer"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Missat samtal"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Missade samtal"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> missade samtal"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 8de488f..f38b2a0 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"Kukata simu"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"Katika simu"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"Nambari yangu ni <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Inapiga"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Simu isiyojibiwa"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Simu zisizojibiwa"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> simu ambazo hazijajibiwa"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index a1b2dee..78f593d 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"กำลังวางสาย"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"กำลังใช้สาย"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"หมายเลขของฉันคือ <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"กำลังโทรออก"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"สายที่ไม่ได้รับ"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"สายที่ไม่ได้รับ"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> สายที่ไม่ได้รับ"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 2919f3d..eaa1968 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"Binababa"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"Nasa tawag"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"Ang aking numero ay <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Dina-dial"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Hindi nasagot na tawag"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Mga hindi nasagot na tawag"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> (na) hindi nasagot na tawag"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 1567c0d..7f014a8 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"Sonlandırılıyor"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"Çağrı halinde"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"Numaram: <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Çevriliyor"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Cevapsız çağrı"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Cevapsız çağrılar"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> cevapsız çağrı"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index f92f403..068ba9e 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"Класти слухавку"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"Вхід. викл."</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"Мій номер: <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Набір"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Пропущ. виклик"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Пропущ. дзвінки"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"Пропущ. дзвінк: <xliff:g id="NUM_MISSED_CALLS">%s</xliff:g>"</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 5881f64..348819c 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"Kết thúc cuộc gọi"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"Đang trong cuộc gọi"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"Số điện thoại của tôi là <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Đang gọi"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Cuộc gọi nhỡ"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Cuộc gọi nhỡ"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> cuộc gọi nhỡ"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 8896425..1d3aafa 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -395,6 +395,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"正在挂断"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"正在通话"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"我的手机号码:<xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"正在拨号"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"未接电话"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"未接电话"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> 个未接电话"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index c0d1ae1..f17a251 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -401,6 +401,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"掛斷電話"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"通話中"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"我的電話號碼:<xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"撥號中"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"未接來電"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"未接來電"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> 通未接來電"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 518d029..d2db86f 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -399,6 +399,7 @@
<string name="card_title_hanging_up" msgid="3999101620995182450">"Iyavala"</string>
<string name="card_title_in_call" msgid="6346543933068225205">"Isecingweni"</string>
<string name="card_title_my_phone_number" msgid="112428362494434191">"Inombolo yami ngu <xliff:g id="MY_PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="notification_dialing" msgid="2107666444937350731">"Iyadayela"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Ikholi ekulahlekele"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Izincingo ezikulahlekele"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> amakholi akulahlekele"</string>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 97a1496..5adfe52 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -41,7 +41,7 @@
<!-- Height of the main row of in-call buttons. -->
<dimen name="in_call_button_height">76dp</dimen>
- <!-- Height of the in-call "End" button. -->
+ <!-- Height of the in-call "End" button. Match with Contact's dimens/call_button_height -->
<dimen name="in_call_end_button_height">74dp</dimen>
<!-- Width of buttons in the extra button row. -->
@@ -69,8 +69,12 @@
<dimen name="dialpad_horizontal_margin">4dp</dimen>
<dimen name="dialpad_vertical_margin">2dp</dimen>
<dimen name="dialpad_digits_text_size">35sp</dimen>
+
<!-- Just used in landscape mode -->
<dimen name="emergency_dialer_digits_height">0px</dimen>
+ <dimen name="dialpad_center_margin">3dp</dimen>
+ <dimen name="dialpad_button_margin">3dp</dimen>
+
<!-- Layout weight values for dialpad screen. These layouts will be used in one
LinearLayout (dialpad_fragment.xml), configuring dialpad screen's vertical
diff --git a/res/values/strings.xml b/res/values/strings.xml
index fe0c5d6..a051173 100755
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -786,6 +786,9 @@
<string name="card_title_my_phone_number">My number is <xliff:g id="my_phone_number">%s</xliff:g></string>
<!-- Notification strings -->
+ <!-- The "label" of the in-call Notification for a dialing call, used
+ as the format string for a Chronometer widget. [CHAR LIMIT=60] -->
+ <string name="notification_dialing">Dialing</string>
<!-- Missed call notification label, used when there's exactly one missed call -->
<string name="notification_missedCallTitle">Missed call</string>
<!-- Missed call notification label, used when there are two or more missed calls -->
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 798cf07..28fb8f2 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -238,4 +238,18 @@
<item name="android:editable">true</item>
<item name="android:cursorVisible">false</item>
</style>
+
+ <style name="PrimaryCallInfoPrimaryCallBanner">
+ <item name="android:layout_alignParentTop">true</item>
+ </style>
+ <style name="PrimaryCallInfoSecondaryInfoContainer">
+ <item name="android:layout_below">@id/primary_call_banner</item>
+ </style>
+
+ <style name="SecondaryCallInfoSecondaryCallName">
+ <item name="android:layout_gravity">top|left</item>
+ </style>
+ <style name="SecondaryCallInfoSecondaryCallStatus">
+ <item name="android:layout_gravity">top|right</item>
+ </style>
</resources>
diff --git a/src/com/android/phone/BitmapUtils.java b/src/com/android/phone/BitmapUtils.java
index 0b8ba99..94d4bf9 100644
--- a/src/com/android/phone/BitmapUtils.java
+++ b/src/com/android/phone/BitmapUtils.java
@@ -28,7 +28,7 @@
public class BitmapUtils {
private static final String TAG = "BitmapUtils";
private static final boolean DBG =
- (PhoneApp.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
+ (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
/** This class is never instantiated. */
private BitmapUtils() {
diff --git a/src/com/android/phone/BluetoothAtPhonebook.java b/src/com/android/phone/BluetoothAtPhonebook.java
deleted file mode 100644
index c9669c4..0000000
--- a/src/com/android/phone/BluetoothAtPhonebook.java
+++ /dev/null
@@ -1,529 +0,0 @@
-/*
- * Copyright (C) 2008 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 com.android.internal.telephony.GsmAlphabet;
-
-import android.bluetooth.AtCommandHandler;
-import android.bluetooth.AtCommandResult;
-import android.bluetooth.AtParser;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.HeadsetBase;
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.CallLog.Calls;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.PhoneLookup;
-import android.telephony.PhoneNumberUtils;
-import android.util.Log;
-
-import java.util.HashMap;
-
-/**
- * Helper for managing phonebook presentation over AT commands
- * @hide
- */
-public class BluetoothAtPhonebook {
- private static final String TAG = "BluetoothAtPhonebook";
- private static final boolean DBG = false;
-
- /** The projection to use when querying the call log database in response
- * to AT+CPBR for the MC, RC, and DC phone books (missed, received, and
- * dialed calls respectively)
- */
- private static final String[] CALLS_PROJECTION = new String[] {
- Calls._ID, Calls.NUMBER
- };
-
- /** The projection to use when querying the contacts database in response
- * to AT+CPBR for the ME phonebook (saved phone numbers).
- */
- private static final String[] PHONES_PROJECTION = new String[] {
- Phone._ID, Phone.DISPLAY_NAME, Phone.NUMBER, Phone.TYPE
- };
-
- /** Android supports as many phonebook entries as the flash can hold, but
- * BT periphals don't. Limit the number we'll report. */
- private static final int MAX_PHONEBOOK_SIZE = 16384;
-
- private static final String OUTGOING_CALL_WHERE = Calls.TYPE + "=" + Calls.OUTGOING_TYPE;
- private static final String INCOMING_CALL_WHERE = Calls.TYPE + "=" + Calls.INCOMING_TYPE;
- private static final String MISSED_CALL_WHERE = Calls.TYPE + "=" + Calls.MISSED_TYPE;
- private static final String VISIBLE_PHONEBOOK_WHERE = Phone.IN_VISIBLE_GROUP + "=1";
-
- private class PhonebookResult {
- public Cursor cursor; // result set of last query
- public int numberColumn;
- public int typeColumn;
- public int nameColumn;
- };
-
- private final Context mContext;
- private final BluetoothHandsfree mHandsfree;
-
- private String mCurrentPhonebook;
- private String mCharacterSet = "UTF-8";
-
- private int mCpbrIndex1, mCpbrIndex2;
- private boolean mCheckingAccessPermission;
-
- // package and class name to which we send intent to check phone book access permission
- private static final String ACCESS_AUTHORITY_PACKAGE = "com.android.settings";
- private static final String ACCESS_AUTHORITY_CLASS =
- "com.android.settings.bluetooth.BluetoothPermissionRequest";
- private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
-
- private final HashMap<String, PhonebookResult> mPhonebooks =
- new HashMap<String, PhonebookResult>(4);
-
- public BluetoothAtPhonebook(Context context, BluetoothHandsfree handsfree) {
- mContext = context;
- mHandsfree = handsfree;
- mPhonebooks.put("DC", new PhonebookResult()); // dialled calls
- mPhonebooks.put("RC", new PhonebookResult()); // received calls
- mPhonebooks.put("MC", new PhonebookResult()); // missed calls
- mPhonebooks.put("ME", new PhonebookResult()); // mobile phonebook
-
- mCurrentPhonebook = "ME"; // default to mobile phonebook
-
- mCpbrIndex1 = mCpbrIndex2 = -1;
- mCheckingAccessPermission = false;
- }
-
- /** Returns the last dialled number, or null if no numbers have been called */
- public String getLastDialledNumber() {
- String[] projection = {Calls.NUMBER};
- Cursor cursor = mContext.getContentResolver().query(Calls.CONTENT_URI, projection,
- Calls.TYPE + "=" + Calls.OUTGOING_TYPE, null, Calls.DEFAULT_SORT_ORDER +
- " LIMIT 1");
- if (cursor == null) return null;
-
- if (cursor.getCount() < 1) {
- cursor.close();
- return null;
- }
- cursor.moveToNext();
- int column = cursor.getColumnIndexOrThrow(Calls.NUMBER);
- String number = cursor.getString(column);
- cursor.close();
- return number;
- }
-
- public void register(AtParser parser) {
- // Select Character Set
- parser.register("+CSCS", new AtCommandHandler() {
- @Override
- public AtCommandResult handleReadCommand() {
- String result = "+CSCS: \"" + mCharacterSet + "\"";
- return new AtCommandResult(result);
- }
- @Override
- public AtCommandResult handleSetCommand(Object[] args) {
- if (args.length < 1) {
- return new AtCommandResult(AtCommandResult.ERROR);
- }
- String characterSet = (String)args[0];
- characterSet = characterSet.replace("\"", "");
- if (characterSet.equals("GSM") || characterSet.equals("IRA") ||
- characterSet.equals("UTF-8") || characterSet.equals("UTF8")) {
- mCharacterSet = characterSet;
- return new AtCommandResult(AtCommandResult.OK);
- } else {
- return mHandsfree.reportCmeError(BluetoothCmeError.OPERATION_NOT_SUPPORTED);
- }
- }
- @Override
- public AtCommandResult handleTestCommand() {
- return new AtCommandResult( "+CSCS: (\"UTF-8\",\"IRA\",\"GSM\")");
- }
- });
-
- // Select PhoneBook memory Storage
- parser.register("+CPBS", new AtCommandHandler() {
- @Override
- public AtCommandResult handleReadCommand() {
- // Return current size and max size
- if ("SM".equals(mCurrentPhonebook)) {
- return new AtCommandResult("+CPBS: \"SM\",0," + getMaxPhoneBookSize(0));
- }
-
- PhonebookResult pbr = getPhonebookResult(mCurrentPhonebook, true);
- if (pbr == null) {
- return mHandsfree.reportCmeError(BluetoothCmeError.OPERATION_NOT_ALLOWED);
- }
- int size = pbr.cursor.getCount();
- return new AtCommandResult("+CPBS: \"" + mCurrentPhonebook + "\"," +
- size + "," + getMaxPhoneBookSize(size));
- }
- @Override
- public AtCommandResult handleSetCommand(Object[] args) {
- // Select phonebook memory
- if (args.length < 1 || !(args[0] instanceof String)) {
- return new AtCommandResult(AtCommandResult.ERROR);
- }
- String pb = ((String)args[0]).trim();
- while (pb.endsWith("\"")) pb = pb.substring(0, pb.length() - 1);
- while (pb.startsWith("\"")) pb = pb.substring(1, pb.length());
- if (getPhonebookResult(pb, false) == null && !"SM".equals(pb)) {
- if (DBG) log("Dont know phonebook: '" + pb + "'");
- return mHandsfree.reportCmeError(BluetoothCmeError.OPERATION_NOT_SUPPORTED);
- }
- mCurrentPhonebook = pb;
- return new AtCommandResult(AtCommandResult.OK);
- }
- @Override
- public AtCommandResult handleTestCommand() {
- return new AtCommandResult("+CPBS: (\"ME\",\"SM\",\"DC\",\"RC\",\"MC\")");
- }
- });
-
- // Read PhoneBook Entries
- parser.register("+CPBR", new AtCommandHandler() {
- @Override
- public AtCommandResult handleSetCommand(Object[] args) {
- // Phone Book Read Request
- // AT+CPBR=<index1>[,<index2>]
-
- if (mCpbrIndex1 != -1) {
- /* handling a CPBR at the moment, reject this CPBR command */
- return mHandsfree.reportCmeError(BluetoothCmeError.OPERATION_NOT_ALLOWED);
- }
-
- // Parse indexes
- int index1;
- int index2;
- if (args.length < 1 || !(args[0] instanceof Integer)) {
- return new AtCommandResult(AtCommandResult.ERROR);
- } else {
- index1 = (Integer)args[0];
- }
-
- if (args.length == 1) {
- index2 = index1;
- } else if (!(args[1] instanceof Integer)) {
- return mHandsfree.reportCmeError(BluetoothCmeError.TEXT_HAS_INVALID_CHARS);
- } else {
- index2 = (Integer)args[1];
- }
-
- mCpbrIndex1 = index1;
- mCpbrIndex2 = index2;
- mCheckingAccessPermission = true;
-
- if (checkAccessPermission()) {
- mCheckingAccessPermission = false;
- AtCommandResult atResult = processCpbrCommand();
- mCpbrIndex1 = mCpbrIndex2 = -1;
- return atResult;
- }
-
- // no reponse here, will continue the process in handleAccessPermissionResult
- return new AtCommandResult(AtCommandResult.UNSOLICITED);
- };
-
- @Override
- public AtCommandResult handleTestCommand() {
- /* Ideally we should return the maximum range of valid index's
- * for the selected phone book, but this causes problems for the
- * Parrot CK3300. So instead send just the range of currently
- * valid index's.
- */
- int size;
- if ("SM".equals(mCurrentPhonebook)) {
- size = 0;
- } else {
- PhonebookResult pbr = getPhonebookResult(mCurrentPhonebook, false);
- if (pbr == null) {
- return mHandsfree.reportCmeError(BluetoothCmeError.OPERATION_NOT_ALLOWED);
- }
- size = pbr.cursor.getCount();
- }
-
- if (size == 0) {
- /* Sending "+CPBR: (1-0)" can confused some carkits, send "1-1"
- * instead */
- size = 1;
- }
- return new AtCommandResult("+CPBR: (1-" + size + "),30,30");
- }
- });
- }
-
- /* package */ void handleAccessPermissionResult(Intent intent) {
- if (!mCheckingAccessPermission) {
- return;
- }
-
- HeadsetBase headset = mHandsfree.getHeadset();
- // ASSERT: (headset != null) && headSet.isConnected()
- // REASON: mCheckingAccessPermission is true, otherwise resetAtState
- // has set mCheckingAccessPermission to false
-
- if (intent.getAction().equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
-
- if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
- BluetoothDevice.CONNECTION_ACCESS_NO) ==
- BluetoothDevice.CONNECTION_ACCESS_YES) {
- BluetoothDevice remoteDevice = headset.getRemoteDevice();
- if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
- remoteDevice.setTrust(true);
- }
-
- AtCommandResult cpbrResult = processCpbrCommand();
- headset.sendURC(cpbrResult.toString());
- } else {
- headset.sendURC("ERROR");
- }
- }
- mCpbrIndex1 = mCpbrIndex2 = -1;
- mCheckingAccessPermission = false;
- }
-
- /** Get the most recent result for the given phone book,
- * with the cursor ready to go.
- * If force then re-query that phonebook
- * Returns null if the cursor is not ready
- */
- private synchronized PhonebookResult getPhonebookResult(String pb, boolean force) {
- if (pb == null) {
- return null;
- }
- PhonebookResult pbr = mPhonebooks.get(pb);
- if (pbr == null) {
- pbr = new PhonebookResult();
- }
- if (force || pbr.cursor == null) {
- if (!queryPhonebook(pb, pbr)) {
- return null;
- }
- }
-
- return pbr;
- }
-
- private synchronized boolean queryPhonebook(String pb, PhonebookResult pbr) {
- String where;
- boolean ancillaryPhonebook = true;
-
- if (pb.equals("ME")) {
- ancillaryPhonebook = false;
- where = VISIBLE_PHONEBOOK_WHERE;
- } else if (pb.equals("DC")) {
- where = OUTGOING_CALL_WHERE;
- } else if (pb.equals("RC")) {
- where = INCOMING_CALL_WHERE;
- } else if (pb.equals("MC")) {
- where = MISSED_CALL_WHERE;
- } else {
- return false;
- }
-
- if (pbr.cursor != null) {
- pbr.cursor.close();
- pbr.cursor = null;
- }
-
- if (ancillaryPhonebook) {
- pbr.cursor = mContext.getContentResolver().query(
- Calls.CONTENT_URI, CALLS_PROJECTION, where, null,
- Calls.DEFAULT_SORT_ORDER + " LIMIT " + MAX_PHONEBOOK_SIZE);
- if (pbr.cursor == null) return false;
-
- pbr.numberColumn = pbr.cursor.getColumnIndexOrThrow(Calls.NUMBER);
- pbr.typeColumn = -1;
- pbr.nameColumn = -1;
- } else {
- pbr.cursor = mContext.getContentResolver().query(Phone.CONTENT_URI, PHONES_PROJECTION,
- where, null, Phone.NUMBER + " LIMIT " + MAX_PHONEBOOK_SIZE);
- if (pbr.cursor == null) return false;
-
- pbr.numberColumn = pbr.cursor.getColumnIndex(Phone.NUMBER);
- pbr.typeColumn = pbr.cursor.getColumnIndex(Phone.TYPE);
- pbr.nameColumn = pbr.cursor.getColumnIndex(Phone.DISPLAY_NAME);
- }
- Log.i(TAG, "Refreshed phonebook " + pb + " with " + pbr.cursor.getCount() + " results");
- return true;
- }
-
- synchronized void resetAtState() {
- mCharacterSet = "UTF-8";
- mCpbrIndex1 = mCpbrIndex2 = -1;
- mCheckingAccessPermission = false;
- }
-
- private synchronized int getMaxPhoneBookSize(int currSize) {
- // some car kits ignore the current size and request max phone book
- // size entries. Thus, it takes a long time to transfer all the
- // entries. Use a heuristic to calculate the max phone book size
- // considering future expansion.
- // maxSize = currSize + currSize / 2 rounded up to nearest power of 2
- // If currSize < 100, use 100 as the currSize
-
- int maxSize = (currSize < 100) ? 100 : currSize;
- maxSize += maxSize / 2;
- return roundUpToPowerOfTwo(maxSize);
- }
-
- private int roundUpToPowerOfTwo(int x) {
- x |= x >> 1;
- x |= x >> 2;
- x |= x >> 4;
- x |= x >> 8;
- x |= x >> 16;
- return x + 1;
- }
-
- // process CPBR command after permission check
- private AtCommandResult processCpbrCommand()
- {
- // Shortcut SM phonebook
- if ("SM".equals(mCurrentPhonebook)) {
- return new AtCommandResult(AtCommandResult.OK);
- }
-
- // Check phonebook
- PhonebookResult pbr = getPhonebookResult(mCurrentPhonebook, false);
- if (pbr == null) {
- return mHandsfree.reportCmeError(BluetoothCmeError.OPERATION_NOT_ALLOWED);
- }
-
- // More sanity checks
- // Send OK instead of ERROR if these checks fail.
- // When we send error, certain kits like BMW disconnect the
- // Handsfree connection.
- if (pbr.cursor.getCount() == 0 || mCpbrIndex1 <= 0 || mCpbrIndex2 < mCpbrIndex1 ||
- mCpbrIndex2 > pbr.cursor.getCount() || mCpbrIndex1 > pbr.cursor.getCount()) {
- return new AtCommandResult(AtCommandResult.OK);
- }
-
- // Process
- AtCommandResult result = new AtCommandResult(AtCommandResult.OK);
- int errorDetected = -1; // no error
- pbr.cursor.moveToPosition(mCpbrIndex1 - 1);
- for (int index = mCpbrIndex1; index <= mCpbrIndex2; index++) {
- String number = pbr.cursor.getString(pbr.numberColumn);
- String name = null;
- int type = -1;
- if (pbr.nameColumn == -1) {
- // try caller id lookup
- // TODO: This code is horribly inefficient. I saw it
- // take 7 seconds to process 100 missed calls.
- Cursor c = mContext.getContentResolver().
- query(Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, number),
- new String[] {PhoneLookup.DISPLAY_NAME, PhoneLookup.TYPE},
- null, null, null);
- if (c != null) {
- if (c.moveToFirst()) {
- name = c.getString(0);
- type = c.getInt(1);
- }
- c.close();
- }
- if (DBG && name == null) log("Caller ID lookup failed for " + number);
-
- } else {
- name = pbr.cursor.getString(pbr.nameColumn);
- }
- if (name == null) name = "";
- name = name.trim();
- if (name.length() > 28) name = name.substring(0, 28);
-
- if (pbr.typeColumn != -1) {
- type = pbr.cursor.getInt(pbr.typeColumn);
- name = name + "/" + getPhoneType(type);
- }
-
- if (number == null) number = "";
- int regionType = PhoneNumberUtils.toaFromString(number);
-
- number = number.trim();
- number = PhoneNumberUtils.stripSeparators(number);
- if (number.length() > 30) number = number.substring(0, 30);
- if (number.equals("-1")) {
- // unknown numbers are stored as -1 in our database
- number = "";
- name = mContext.getString(R.string.unknown);
- }
-
- // TODO(): Handle IRA commands. It's basically
- // a 7 bit ASCII character set.
- if (!name.equals("") && mCharacterSet.equals("GSM")) {
- byte[] nameByte = GsmAlphabet.stringToGsm8BitPacked(name);
- if (nameByte == null) {
- name = mContext.getString(R.string.unknown);
- } else {
- name = new String(nameByte);
- }
- }
-
- result.addResponse("+CPBR: " + index + ",\"" + number + "\"," +
- regionType + ",\"" + name + "\"");
- if (!pbr.cursor.moveToNext()) {
- break;
- }
- }
- return result;
- }
-
- // Check if the remote device has premission to read our phone book
- // Return true if it has the permission
- // false if not known and we have sent our Intent to check
- private boolean checkAccessPermission() {
- BluetoothDevice remoteDevice = mHandsfree.getHeadset().getRemoteDevice();
-
- boolean trust = remoteDevice.getTrustState();
-
- if (trust) {
- return true;
- }
-
- Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST);
- intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS);
- intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
- BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, remoteDevice);
- // Leave EXTRA_PACKAGE_NAME and EXTRA_CLASS_NAME field empty
- // BluetoothHandsfree's broadcast receiver is anonymous, cannot be targeted
- mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
-
- return false;
- }
-
- private static String getPhoneType(int type) {
- switch (type) {
- case Phone.TYPE_HOME:
- return "H";
- case Phone.TYPE_MOBILE:
- return "M";
- case Phone.TYPE_WORK:
- return "W";
- case Phone.TYPE_FAX_HOME:
- case Phone.TYPE_FAX_WORK:
- return "F";
- case Phone.TYPE_OTHER:
- case Phone.TYPE_CUSTOM:
- default:
- return "O";
- }
- }
-
- private static void log(String msg) {
- Log.d(TAG, msg);
- }
-}
diff --git a/src/com/android/phone/BluetoothCmeError.java b/src/com/android/phone/BluetoothCmeError.java
deleted file mode 100644
index aa5e887..0000000
--- a/src/com/android/phone/BluetoothCmeError.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2008 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;
-
-/* Constants for extended AT error codes specified by the Handsfree profile. */
-public class BluetoothCmeError {
- public static final int AG_FAILURE = 0;
- public static final int NO_CONNECTION_TO_PHONE = 1;
- public static final int OPERATION_NOT_ALLOWED = 3;
- public static final int OPERATION_NOT_SUPPORTED = 4;
- public static final int PIN_REQUIRED = 5;
- public static final int SIM_MISSING = 10;
- public static final int SIM_PIN_REQUIRED = 11;
- public static final int SIM_PUK_REQUIRED = 12;
- public static final int SIM_FAILURE = 13;
- public static final int SIM_BUSY = 14;
- public static final int WRONG_PASSWORD = 16;
- public static final int SIM_PIN2_REQUIRED = 17;
- public static final int SIM_PUK2_REQUIRED = 18;
- public static final int MEMORY_FULL = 20;
- public static final int INVALID_INDEX = 21;
- public static final int MEMORY_FAILURE = 23;
- public static final int TEXT_TOO_LONG = 24;
- public static final int TEXT_HAS_INVALID_CHARS = 25;
- public static final int DIAL_STRING_TOO_LONG = 26;
- public static final int DIAL_STRING_HAS_INVALID_CHARS = 27;
- public static final int NO_SERVICE = 30;
- public static final int ONLY_911_ALLOWED = 32;
-}
diff --git a/src/com/android/phone/BluetoothHandsfree.java b/src/com/android/phone/BluetoothHandsfree.java
deleted file mode 100755
index 5845397..0000000
--- a/src/com/android/phone/BluetoothHandsfree.java
+++ /dev/null
@@ -1,3071 +0,0 @@
-/*
- * Copyright (C) 2008 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.bluetooth.AtCommandHandler;
-import android.bluetooth.AtCommandResult;
-import android.bluetooth.AtParser;
-import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothAssignedNumbers;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHeadset;
-import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothServerSocket;
-import android.bluetooth.BluetoothSocket;
-import android.bluetooth.HeadsetBase;
-import android.content.ActivityNotFoundException;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.media.AudioManager;
-import android.net.Uri;
-import android.os.AsyncResult;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
-import android.os.SystemProperties;
-import android.telephony.PhoneNumberUtils;
-import android.telephony.ServiceState;
-import android.telephony.SignalStrength;
-import android.telephony.TelephonyManager;
-import android.util.Log;
-
-import com.android.internal.telephony.Call;
-import com.android.internal.telephony.Connection;
-import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.telephony.CallManager;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.LinkedList;
-
-/**
- * Bluetooth headset manager for the Phone app.
- * @hide
- */
-public class BluetoothHandsfree {
- private static final String TAG = "Bluetooth HS/HF";
- private static final boolean DBG = (PhoneApp.DBG_LEVEL >= 1)
- && (SystemProperties.getInt("ro.debuggable", 0) == 1);
- private static final boolean VDBG = (PhoneApp.DBG_LEVEL >= 2); // even more logging
-
- public static final int TYPE_UNKNOWN = 0;
- public static final int TYPE_HEADSET = 1;
- public static final int TYPE_HANDSFREE = 2;
-
- /** The singleton instance. */
- private static BluetoothHandsfree sInstance;
-
- private final Context mContext;
- private final BluetoothAdapter mAdapter;
- private final CallManager mCM;
- private BluetoothA2dp mA2dp;
-
- private BluetoothDevice mA2dpDevice;
- private int mA2dpState;
- private boolean mPendingAudioState;
- private int mAudioState;
-
- private ServiceState mServiceState;
- private HeadsetBase mHeadset;
- private BluetoothHeadset mBluetoothHeadset;
- private int mHeadsetType; // TYPE_UNKNOWN when not connected
- private boolean mAudioPossible;
- private BluetoothSocket mConnectedSco;
-
- private IncomingScoAcceptThread mIncomingScoThread = null;
- private ScoSocketConnectThread mConnectScoThread = null;
- private SignalScoCloseThread mSignalScoCloseThread = null;
-
- private AudioManager mAudioManager;
- private PowerManager mPowerManager;
-
- private boolean mPendingSco; // waiting for a2dp sink to suspend before establishing SCO
- private boolean mA2dpSuspended;
- private boolean mUserWantsAudio;
- private WakeLock mStartCallWakeLock; // held while waiting for the intent to start call
- private WakeLock mStartVoiceRecognitionWakeLock; // held while waiting for voice recognition
-
- // AT command state
- private static final int GSM_MAX_CONNECTIONS = 6; // Max connections allowed by GSM
- private static final int CDMA_MAX_CONNECTIONS = 2; // Max connections allowed by CDMA
-
- private long mBgndEarliestConnectionTime = 0;
- private boolean mClip = false; // Calling Line Information Presentation
- private boolean mIndicatorsEnabled = false;
- private boolean mCmee = false; // Extended Error reporting
- private long[] mClccTimestamps; // Timestamps associated with each clcc index
- private boolean[] mClccUsed; // Is this clcc index in use
- private boolean mWaitingForCallStart;
- private boolean mWaitingForVoiceRecognition;
- // do not connect audio until service connection is established
- // for 3-way supported devices, this is after AT+CHLD
- // for non-3-way supported devices, this is after AT+CMER (see spec)
- private boolean mServiceConnectionEstablished;
-
- private final BluetoothPhoneState mBluetoothPhoneState; // for CIND and CIEV updates
- private final BluetoothAtPhonebook mPhonebook;
- private PhoneConstants.State mPhoneState = PhoneConstants.State.IDLE;
- CdmaPhoneCallState.PhoneCallState mCdmaThreeWayCallState =
- CdmaPhoneCallState.PhoneCallState.IDLE;
-
- private DebugThread mDebugThread;
- private int mScoGain = Integer.MIN_VALUE;
-
- private static Intent sVoiceCommandIntent;
-
- // Audio parameters
- private static final String HEADSET_NREC = "bt_headset_nrec";
- private static final String HEADSET_NAME = "bt_headset_name";
-
- private int mRemoteBrsf = 0;
- private int mLocalBrsf = 0;
-
- // CDMA specific flag used in context with BT devices having display capabilities
- // to show which Caller is active. This state might not be always true as in CDMA
- // networks if a caller drops off no update is provided to the Phone.
- // This flag is just used as a toggle to provide a update to the BT device to specify
- // which caller is active.
- private boolean mCdmaIsSecondCallActive = false;
- private boolean mCdmaCallsSwapped = false;
-
- /* Constants from Bluetooth Specification Hands-Free profile version 1.5 */
- private static final int BRSF_AG_THREE_WAY_CALLING = 1 << 0;
- private static final int BRSF_AG_EC_NR = 1 << 1;
- private static final int BRSF_AG_VOICE_RECOG = 1 << 2;
- private static final int BRSF_AG_IN_BAND_RING = 1 << 3;
- private static final int BRSF_AG_VOICE_TAG_NUMBE = 1 << 4;
- private static final int BRSF_AG_REJECT_CALL = 1 << 5;
- private static final int BRSF_AG_ENHANCED_CALL_STATUS = 1 << 6;
- private static final int BRSF_AG_ENHANCED_CALL_CONTROL = 1 << 7;
- private static final int BRSF_AG_ENHANCED_ERR_RESULT_CODES = 1 << 8;
-
- private static final int BRSF_HF_EC_NR = 1 << 0;
- private static final int BRSF_HF_CW_THREE_WAY_CALLING = 1 << 1;
- private static final int BRSF_HF_CLIP = 1 << 2;
- private static final int BRSF_HF_VOICE_REG_ACT = 1 << 3;
- private static final int BRSF_HF_REMOTE_VOL_CONTROL = 1 << 4;
- private static final int BRSF_HF_ENHANCED_CALL_STATUS = 1 << 5;
- private static final int BRSF_HF_ENHANCED_CALL_CONTROL = 1 << 6;
-
- // VirtualCall - true if Virtual Call is active, false otherwise
- private boolean mVirtualCallStarted = false;
-
- // Voice Recognition - true if Voice Recognition is active, false otherwise
- private boolean mVoiceRecognitionStarted;
-
- private HandsfreeMessageHandler mHandler;
-
- public static String typeToString(int type) {
- switch (type) {
- case TYPE_UNKNOWN:
- return "unknown";
- case TYPE_HEADSET:
- return "headset";
- case TYPE_HANDSFREE:
- return "handsfree";
- }
- return null;
- }
-
- /**
- * Initialize the singleton BluetoothHandsfree instance.
- * This is only done once, at startup, from PhoneApp.onCreate().
- */
- /* package */ static BluetoothHandsfree init(Context context, CallManager cm) {
- synchronized (BluetoothHandsfree.class) {
- if (sInstance == null) {
- sInstance = new BluetoothHandsfree(context, cm);
- } else {
- Log.wtf(TAG, "init() called multiple times! sInstance = " + sInstance);
- }
- return sInstance;
- }
- }
-
- /** Private constructor; @see init() */
- private BluetoothHandsfree(Context context, CallManager cm) {
- mCM = cm;
- mContext = context;
- mAdapter = BluetoothAdapter.getDefaultAdapter();
- boolean bluetoothCapable = (mAdapter != null);
- mHeadset = null;
- mHeadsetType = TYPE_UNKNOWN; // nothing connected yet
- if (bluetoothCapable) {
- mAdapter.getProfileProxy(mContext, mProfileListener,
- BluetoothProfile.A2DP);
- }
- mA2dpState = BluetoothA2dp.STATE_DISCONNECTED;
- mA2dpDevice = null;
- mA2dpSuspended = false;
-
- mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
- mStartCallWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
- TAG + ":StartCall");
- mStartCallWakeLock.setReferenceCounted(false);
- mStartVoiceRecognitionWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
- TAG + ":VoiceRecognition");
- mStartVoiceRecognitionWakeLock.setReferenceCounted(false);
-
- mLocalBrsf = BRSF_AG_THREE_WAY_CALLING |
- BRSF_AG_EC_NR |
- BRSF_AG_REJECT_CALL |
- BRSF_AG_ENHANCED_CALL_STATUS;
-
- if (sVoiceCommandIntent == null) {
- sVoiceCommandIntent = new Intent(Intent.ACTION_VOICE_COMMAND);
- sVoiceCommandIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- }
- if (mContext.getPackageManager().resolveActivity(sVoiceCommandIntent, 0) != null &&
- BluetoothHeadset.isBluetoothVoiceDialingEnabled(mContext)) {
- mLocalBrsf |= BRSF_AG_VOICE_RECOG;
- }
-
- HandlerThread thread = new HandlerThread("BluetoothHandsfreeHandler");
- thread.start();
- Looper looper = thread.getLooper();
- mHandler = new HandsfreeMessageHandler(looper);
- mBluetoothPhoneState = new BluetoothPhoneState();
- mUserWantsAudio = true;
- mVirtualCallStarted = false;
- mVoiceRecognitionStarted = false;
- mPhonebook = new BluetoothAtPhonebook(mContext, this);
- mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- cdmaSetSecondCallState(false);
-
- if (bluetoothCapable) {
- resetAtState();
- }
-
- }
-
- /**
- * A thread that runs in the background waiting for a Sco Server Socket to
- * accept a connection. Even after a connection has been accepted, the Sco Server
- * continues to listen for new connections.
- */
- private class IncomingScoAcceptThread extends Thread{
- private final BluetoothServerSocket mIncomingServerSocket;
- private BluetoothSocket mIncomingSco;
- private boolean stopped = false;
-
- public IncomingScoAcceptThread() {
- BluetoothServerSocket serverSocket = null;
- try {
- serverSocket = BluetoothAdapter.listenUsingScoOn();
- } catch (IOException e) {
- Log.e(TAG, "Could not create BluetoothServerSocket");
- stopped = true;
- }
- mIncomingServerSocket = serverSocket;
- }
-
- @Override
- public void run() {
- while (!stopped) {
- try {
- mIncomingSco = mIncomingServerSocket.accept();
- } catch (IOException e) {
- Log.e(TAG, "BluetoothServerSocket could not accept connection");
- }
-
- if (mIncomingSco != null) {
- connectSco();
- }
- }
- }
-
- private void connectSco() {
- synchronized (BluetoothHandsfree.this) {
- if (!Thread.interrupted() && isHeadsetConnected() &&
- (mAudioPossible || allowAudioAnytime()) &&
- mConnectedSco == null) {
- Log.i(TAG, "Routing audio for incoming SCO connection");
- mConnectedSco = mIncomingSco;
- mAudioManager.setBluetoothScoOn(true);
- setAudioState(BluetoothHeadset.STATE_AUDIO_CONNECTED,
- mHeadset.getRemoteDevice());
-
- if (mSignalScoCloseThread == null) {
- mSignalScoCloseThread = new SignalScoCloseThread();
- mSignalScoCloseThread.setName("SignalScoCloseThread");
- mSignalScoCloseThread.start();
- }
- } else {
- Log.i(TAG, "Rejecting incoming SCO connection");
- try {
- mIncomingSco.close();
- }catch (IOException e) {
- Log.e(TAG, "Error when closing incoming Sco socket");
- }
- mIncomingSco = null;
- }
- }
- }
-
- // must be called with BluetoothHandsfree locked
- void shutdown() {
- try {
- mIncomingServerSocket.close();
- } catch (IOException e) {
- Log.w(TAG, "Error when closing server socket");
- }
- stopped = true;
- interrupt();
- }
- }
-
- /**
- * A thread that runs in the background waiting for a Sco Socket to
- * connect.Once the socket is connected, this thread shall be
- * shutdown.
- */
- private class ScoSocketConnectThread extends Thread{
- private BluetoothSocket mOutgoingSco;
-
- public ScoSocketConnectThread(BluetoothDevice device) {
- try {
- mOutgoingSco = device.createScoSocket();
- } catch (IOException e) {
- Log.w(TAG, "Could not create BluetoothSocket");
- failedScoConnect();
- }
- }
-
- @Override
- public void run() {
- try {
- mOutgoingSco.connect();
- }catch (IOException connectException) {
- Log.e(TAG, "BluetoothSocket could not connect");
- mOutgoingSco = null;
- failedScoConnect();
- }
-
- if (mOutgoingSco != null) {
- connectSco();
- }
- }
-
- private void connectSco() {
- synchronized (BluetoothHandsfree.this) {
- if (!Thread.interrupted() && isHeadsetConnected() && mConnectedSco == null) {
- if (VDBG) log("Routing audio for outgoing SCO conection");
- mConnectedSco = mOutgoingSco;
- mAudioManager.setBluetoothScoOn(true);
-
- setAudioState(BluetoothHeadset.STATE_AUDIO_CONNECTED,
- mHeadset.getRemoteDevice());
-
- if (mSignalScoCloseThread == null) {
- mSignalScoCloseThread = new SignalScoCloseThread();
- mSignalScoCloseThread.setName("SignalScoCloseThread");
- mSignalScoCloseThread.start();
- }
- } else {
- if (VDBG) log("Rejecting new connected outgoing SCO socket");
- try {
- mOutgoingSco.close();
- }catch (IOException e) {
- Log.e(TAG, "Error when closing Sco socket");
- }
- mOutgoingSco = null;
- failedScoConnect();
- }
- }
- }
-
- private void failedScoConnect() {
- // Wait for couple of secs before sending AUDIO_STATE_DISCONNECTED,
- // since an incoming SCO connection can happen immediately with
- // certain headsets.
- Message msg = Message.obtain(mHandler, SCO_AUDIO_STATE);
- msg.obj = mHeadset.getRemoteDevice();
- mHandler.sendMessageDelayed(msg, 2000);
-
- // Sync with interrupt() statement of shutdown method
- // This prevents resetting of a valid mConnectScoThread.
- // If this thread has been interrupted, it has been shutdown and
- // mConnectScoThread is/will be reset by the outer class.
- // We do not want to do it here since mConnectScoThread could be
- // assigned with a new object.
- synchronized (ScoSocketConnectThread.this) {
- if (!isInterrupted()) {
- resetConnectScoThread();
- }
- }
- }
-
- // must be called with BluetoothHandsfree locked
- void shutdown() {
- closeConnectedSco();
-
- // sync with isInterrupted() check in failedScoConnect method
- // see explanation there
- synchronized (ScoSocketConnectThread.this) {
- interrupt();
- }
- }
- }
-
- /*
- * Signals when a Sco connection has been closed
- */
- private class SignalScoCloseThread extends Thread{
- private boolean stopped = false;
-
- @Override
- public void run() {
- while (!stopped) {
- BluetoothSocket connectedSco = null;
- synchronized (BluetoothHandsfree.this) {
- connectedSco = mConnectedSco;
- }
- if (connectedSco != null) {
- byte b[] = new byte[1];
- InputStream inStream = null;
- try {
- inStream = connectedSco.getInputStream();
- } catch (IOException e) {}
-
- if (inStream != null) {
- try {
- // inStream.read is a blocking call that won't ever
- // return anything, but will throw an exception if the
- // connection is closed
- int ret = inStream.read(b, 0, 1);
- }catch (IOException connectException) {
- // call a message to close this thread and turn off audio
- // we can't call audioOff directly because then
- // the thread would try to close itself
- Message msg = Message.obtain(mHandler, SCO_CLOSED);
- mHandler.sendMessage(msg);
- break;
- }
- }
- }
- }
- }
-
- // must be called with BluetoothHandsfree locked
- void shutdown() {
- stopped = true;
- closeConnectedSco();
- interrupt();
- }
- }
-
- private void connectScoThread(){
- // Sync with setting mConnectScoThread to null to assure the validity of
- // the condition
- synchronized (ScoSocketConnectThread.class) {
- if (mConnectScoThread == null) {
- BluetoothDevice device = mHeadset.getRemoteDevice();
- if (getAudioState(device) == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
- setAudioState(BluetoothHeadset.STATE_AUDIO_CONNECTING, device);
- }
-
- mConnectScoThread = new ScoSocketConnectThread(mHeadset.getRemoteDevice());
- mConnectScoThread.setName("HandsfreeScoSocketConnectThread");
-
- mConnectScoThread.start();
- }
- }
- }
-
- private void resetConnectScoThread() {
- // Sync with if (mConnectScoThread == null) check
- synchronized (ScoSocketConnectThread.class) {
- mConnectScoThread = null;
- }
- }
-
- // must be called with BluetoothHandsfree locked
- private void closeConnectedSco() {
- if (mConnectedSco != null) {
- try {
- mConnectedSco.close();
- } catch (IOException e) {
- Log.e(TAG, "Error when closing Sco socket");
- }
-
- BluetoothDevice device = null;
- if (mHeadset != null) {
- device = mHeadset.getRemoteDevice();
- }
- mAudioManager.setBluetoothScoOn(false);
- setAudioState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, device);
-
- mConnectedSco = null;
- }
- }
-
- /* package */ synchronized void onBluetoothEnabled() {
- /* Bluez has a bug where it will always accept and then orphan
- * incoming SCO connections, regardless of whether we have a listening
- * SCO socket. So the best thing to do is always run a listening socket
- * while bluetooth is on so that at least we can disconnect it
- * immediately when we don't want it.
- */
-
- if (mIncomingScoThread == null) {
- mIncomingScoThread = new IncomingScoAcceptThread();
- mIncomingScoThread.setName("incomingScoAcceptThread");
- mIncomingScoThread.start();
- }
- }
-
- /* package */ synchronized void onBluetoothDisabled() {
- // Close off the SCO sockets
- audioOff();
-
- if (mIncomingScoThread != null) {
- mIncomingScoThread.shutdown();
- mIncomingScoThread = null;
- }
- }
-
- private boolean isHeadsetConnected() {
- if (mHeadset == null || mHeadsetType == TYPE_UNKNOWN) {
- return false;
- }
- return mHeadset.isConnected();
- }
-
- /* package */ synchronized void connectHeadset(HeadsetBase headset, int headsetType) {
- mHeadset = headset;
- mHeadsetType = headsetType;
- if (mHeadsetType == TYPE_HEADSET) {
- initializeHeadsetAtParser();
- } else {
- initializeHandsfreeAtParser();
- }
-
- // Headset vendor-specific commands
- registerAllVendorSpecificCommands();
-
- headset.startEventThread();
- configAudioParameters();
-
- if (inDebug()) {
- startDebug();
- }
-
- if (isIncallAudio()) {
- audioOn();
- } else if ( mCM.getFirstActiveRingingCall().isRinging()) {
- // need to update HS with RING when single ringing call exist
- mBluetoothPhoneState.ring();
- }
- }
-
- /* returns true if there is some kind of in-call audio we may wish to route
- * bluetooth to */
- private boolean isIncallAudio() {
- Call.State state = mCM.getActiveFgCallState();
-
- return (state == Call.State.ACTIVE || state == Call.State.ALERTING);
- }
-
- /* package */ synchronized void disconnectHeadset() {
- audioOff();
-
- // No need to check if isVirtualCallInProgress()
- // terminateScoUsingVirtualVoiceCall() does the check
- terminateScoUsingVirtualVoiceCall();
-
- mHeadsetType = TYPE_UNKNOWN;
- stopDebug();
- resetAtState();
- }
-
- /* package */ synchronized void resetAtState() {
- mClip = false;
- mIndicatorsEnabled = false;
- mServiceConnectionEstablished = false;
- mCmee = false;
- mClccTimestamps = new long[GSM_MAX_CONNECTIONS];
- mClccUsed = new boolean[GSM_MAX_CONNECTIONS];
- for (int i = 0; i < GSM_MAX_CONNECTIONS; i++) {
- mClccUsed[i] = false;
- }
- mRemoteBrsf = 0;
- mPhonebook.resetAtState();
- }
-
- /* package */ HeadsetBase getHeadset() {
- return mHeadset;
- }
-
- private void configAudioParameters() {
- String name = mHeadset.getRemoteDevice().getName();
- if (name == null) {
- name = "<unknown>";
- }
- mAudioManager.setParameters(HEADSET_NAME+"="+name+";"+HEADSET_NREC+"=on");
- }
-
-
- /** Represents the data that we send in a +CIND or +CIEV command to the HF
- */
- private class BluetoothPhoneState {
- // 0: no service
- // 1: service
- private int mService;
-
- // 0: no active call
- // 1: active call (where active means audio is routed - not held call)
- private int mCall;
-
- // 0: not in call setup
- // 1: incoming call setup
- // 2: outgoing call setup
- // 3: remote party being alerted in an outgoing call setup
- private int mCallsetup;
-
- // 0: no calls held
- // 1: held call and active call
- // 2: held call only
- private int mCallheld;
-
- // cellular signal strength of AG: 0-5
- private int mSignal;
-
- // cellular signal strength in CSQ rssi scale
- private int mRssi; // for CSQ
-
- // 0: roaming not active (home)
- // 1: roaming active
- private int mRoam;
-
- // battery charge of AG: 0-5
- private int mBattchg;
-
- // 0: not registered
- // 1: registered, home network
- // 5: registered, roaming
- private int mStat; // for CREG
-
- private String mRingingNumber; // Context for in-progress RING's
- private int mRingingType;
- private boolean mIgnoreRing = false;
- private boolean mStopRing = false;
-
- // current or last call start timestamp
- private long mCallStartTime = 0;
- // time window to reconnect remotely-disconnected SCO
- // in mili-seconds
- private static final int RETRY_SCO_TIME_WINDOW = 1000;
-
- private static final int SERVICE_STATE_CHANGED = 1;
- private static final int PRECISE_CALL_STATE_CHANGED = 2;
- private static final int RING = 3;
- private static final int PHONE_CDMA_CALL_WAITING = 4;
-
- private Handler mStateChangeHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch(msg.what) {
- case RING:
- AtCommandResult result = ring();
- if (result != null) {
- sendURC(result.toString());
- }
- break;
- case SERVICE_STATE_CHANGED:
- ServiceState state = (ServiceState) ((AsyncResult) msg.obj).result;
- updateServiceState(sendUpdate(), state);
- break;
- case PRECISE_CALL_STATE_CHANGED:
- case PHONE_CDMA_CALL_WAITING:
- Connection connection = null;
- if (((AsyncResult) msg.obj).result instanceof Connection) {
- connection = (Connection) ((AsyncResult) msg.obj).result;
- }
- handlePreciseCallStateChange(sendUpdate(), connection);
- break;
- }
- }
- };
-
- private BluetoothPhoneState() {
- // init members
- // TODO May consider to repalce the default phone's state and signal
- // by CallManagter's state and signal
- updateServiceState(false, mCM.getDefaultPhone().getServiceState());
- handlePreciseCallStateChange(false, null);
- mBattchg = 5; // There is currently no API to get battery level
- // on demand, so set to 5 and wait for an update
- mSignal = asuToSignal(mCM.getDefaultPhone().getSignalStrength());
-
- // register for updates
- // Use the service state of default phone as BT service state to
- // avoid situation such as no cell or wifi connection but still
- // reporting in service (since SipPhone always reports in service).
- mCM.getDefaultPhone().registerForServiceStateChanged(mStateChangeHandler,
- SERVICE_STATE_CHANGED, null);
- mCM.registerForPreciseCallStateChanged(mStateChangeHandler,
- PRECISE_CALL_STATE_CHANGED, null);
- mCM.registerForCallWaiting(mStateChangeHandler,
- PHONE_CDMA_CALL_WAITING, null);
-
- IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
- filter.addAction(TelephonyIntents.ACTION_SIGNAL_STRENGTH_CHANGED);
- filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
- filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
- mContext.registerReceiver(mStateReceiver, filter);
- }
-
- private void updateBtPhoneStateAfterRadioTechnologyChange() {
- if(VDBG) Log.d(TAG, "updateBtPhoneStateAfterRadioTechnologyChange...");
-
- //Unregister all events from the old obsolete phone
- mCM.getDefaultPhone().unregisterForServiceStateChanged(mStateChangeHandler);
- mCM.unregisterForPreciseCallStateChanged(mStateChangeHandler);
- mCM.unregisterForCallWaiting(mStateChangeHandler);
-
- //Register all events new to the new active phone
- mCM.getDefaultPhone().registerForServiceStateChanged(mStateChangeHandler,
- SERVICE_STATE_CHANGED, null);
- mCM.registerForPreciseCallStateChanged(mStateChangeHandler,
- PRECISE_CALL_STATE_CHANGED, null);
- mCM.registerForCallWaiting(mStateChangeHandler,
- PHONE_CDMA_CALL_WAITING, null);
- }
-
- private boolean sendUpdate() {
- return isHeadsetConnected() && mHeadsetType == TYPE_HANDSFREE && mIndicatorsEnabled
- && mServiceConnectionEstablished;
- }
-
- private boolean sendClipUpdate() {
- return isHeadsetConnected() && mHeadsetType == TYPE_HANDSFREE && mClip &&
- mServiceConnectionEstablished;
- }
-
- private boolean sendRingUpdate() {
- if (isHeadsetConnected() && !mIgnoreRing && !mStopRing &&
- mCM.getFirstActiveRingingCall().isRinging()) {
- if (mHeadsetType == TYPE_HANDSFREE) {
- return mServiceConnectionEstablished ? true : false;
- }
- return true;
- }
- return false;
- }
-
- private void stopRing() {
- mStopRing = true;
- }
-
- /* convert [0,31] ASU signal strength to the [0,5] expected by
- * bluetooth devices. Scale is similar to status bar policy
- */
- private int gsmAsuToSignal(SignalStrength signalStrength) {
- int asu = signalStrength.getGsmSignalStrength();
- if (asu >= 16) return 5;
- else if (asu >= 8) return 4;
- else if (asu >= 4) return 3;
- else if (asu >= 2) return 2;
- else if (asu >= 1) return 1;
- else return 0;
- }
-
- /**
- * Convert the cdma / evdo db levels to appropriate icon level.
- * The scale is similar to the one used in status bar policy.
- *
- * @param signalStrength
- * @return the icon level
- */
- private int cdmaDbmEcioToSignal(SignalStrength signalStrength) {
- int levelDbm = 0;
- int levelEcio = 0;
- int cdmaIconLevel = 0;
- int evdoIconLevel = 0;
- int cdmaDbm = signalStrength.getCdmaDbm();
- int cdmaEcio = signalStrength.getCdmaEcio();
-
- if (cdmaDbm >= -75) levelDbm = 4;
- else if (cdmaDbm >= -85) levelDbm = 3;
- else if (cdmaDbm >= -95) levelDbm = 2;
- else if (cdmaDbm >= -100) levelDbm = 1;
- else levelDbm = 0;
-
- // Ec/Io are in dB*10
- if (cdmaEcio >= -90) levelEcio = 4;
- else if (cdmaEcio >= -110) levelEcio = 3;
- else if (cdmaEcio >= -130) levelEcio = 2;
- else if (cdmaEcio >= -150) levelEcio = 1;
- else levelEcio = 0;
-
- cdmaIconLevel = (levelDbm < levelEcio) ? levelDbm : levelEcio;
-
- if (mServiceState != null &&
- (mServiceState.getNetworkType() == TelephonyManager.NETWORK_TYPE_EVDO_0 ||
- mServiceState.getNetworkType() == TelephonyManager.NETWORK_TYPE_EVDO_A)) {
- int evdoEcio = signalStrength.getEvdoEcio();
- int evdoSnr = signalStrength.getEvdoSnr();
- int levelEvdoEcio = 0;
- int levelEvdoSnr = 0;
-
- // Ec/Io are in dB*10
- if (evdoEcio >= -650) levelEvdoEcio = 4;
- else if (evdoEcio >= -750) levelEvdoEcio = 3;
- else if (evdoEcio >= -900) levelEvdoEcio = 2;
- else if (evdoEcio >= -1050) levelEvdoEcio = 1;
- else levelEvdoEcio = 0;
-
- if (evdoSnr > 7) levelEvdoSnr = 4;
- else if (evdoSnr > 5) levelEvdoSnr = 3;
- else if (evdoSnr > 3) levelEvdoSnr = 2;
- else if (evdoSnr > 1) levelEvdoSnr = 1;
- else levelEvdoSnr = 0;
-
- evdoIconLevel = (levelEvdoEcio < levelEvdoSnr) ? levelEvdoEcio : levelEvdoSnr;
- }
- // TODO(): There is a bug open regarding what should be sent.
- return (cdmaIconLevel > evdoIconLevel) ? cdmaIconLevel : evdoIconLevel;
-
- }
-
-
- private int asuToSignal(SignalStrength signalStrength) {
- if (signalStrength.isGsm()) {
- return gsmAsuToSignal(signalStrength);
- } else {
- return cdmaDbmEcioToSignal(signalStrength);
- }
- }
-
-
- /* convert [0,5] signal strength to a rssi signal strength for CSQ
- * which is [0,31]. Despite the same scale, this is not the same value
- * as ASU.
- */
- private int signalToRssi(int signal) {
- // using C4A suggested values
- switch (signal) {
- case 0: return 0;
- case 1: return 4;
- case 2: return 8;
- case 3: return 13;
- case 4: return 19;
- case 5: return 31;
- }
- return 0;
- }
-
-
- private final BroadcastReceiver mStateReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(Intent.ACTION_BATTERY_CHANGED)) {
- Message msg = mHandler.obtainMessage(BATTERY_CHANGED, intent);
- mHandler.sendMessage(msg);
- } else if (intent.getAction().equals(
- TelephonyIntents.ACTION_SIGNAL_STRENGTH_CHANGED)) {
- Message msg = mHandler.obtainMessage(SIGNAL_STRENGTH_CHANGED,
- intent);
- mHandler.sendMessage(msg);
- } else if (intent.getAction().equals(
- BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) {
- int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
- BluetoothProfile.STATE_DISCONNECTED);
- int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE,
- BluetoothProfile.STATE_DISCONNECTED);
- BluetoothDevice device =
- intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-
-
- // We are only concerned about Connected sinks to suspend and resume
- // them. We can safely ignore SINK_STATE_CHANGE for other devices.
- if (device == null || (mA2dpDevice != null && !device.equals(mA2dpDevice))) {
- return;
- }
-
- synchronized (BluetoothHandsfree.this) {
- mA2dpState = state;
- if (state == BluetoothProfile.STATE_DISCONNECTED) {
- mA2dpDevice = null;
- } else {
- mA2dpDevice = device;
- }
- if (oldState == BluetoothA2dp.STATE_PLAYING &&
- mA2dpState == BluetoothProfile.STATE_CONNECTED) {
- if (mA2dpSuspended) {
- if (mPendingSco) {
- mHandler.removeMessages(MESSAGE_CHECK_PENDING_SCO);
- if (DBG) log("A2DP suspended, completing SCO");
- connectScoThread();
- mPendingSco = false;
- }
- }
- }
- }
- } else if (intent.getAction().
- equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
- mPhonebook.handleAccessPermissionResult(intent);
- }
- }
- };
-
- private synchronized void updateBatteryState(Intent intent) {
- int batteryLevel = intent.getIntExtra("level", -1);
- int scale = intent.getIntExtra("scale", -1);
- if (batteryLevel == -1 || scale == -1) {
- return; // ignore
- }
- batteryLevel = batteryLevel * 5 / scale;
- if (mBattchg != batteryLevel) {
- mBattchg = batteryLevel;
- if (sendUpdate()) {
- sendURC("+CIEV: 7," + mBattchg);
- }
- }
- }
-
- private synchronized void updateSignalState(Intent intent) {
- // NOTE this function is called by the BroadcastReceiver mStateReceiver after intent
- // ACTION_SIGNAL_STRENGTH_CHANGED and by the DebugThread mDebugThread
- if (!isHeadsetConnected()) {
- return;
- }
-
- SignalStrength signalStrength = SignalStrength.newFromBundle(intent.getExtras());
- int signal;
-
- if (signalStrength != null) {
- signal = asuToSignal(signalStrength);
- mRssi = signalToRssi(signal); // no unsolicited CSQ
- if (signal != mSignal) {
- mSignal = signal;
- if (sendUpdate()) {
- sendURC("+CIEV: 5," + mSignal);
- }
- }
- } else {
- Log.e(TAG, "Signal Strength null");
- }
- }
-
- private synchronized void updateServiceState(boolean sendUpdate, ServiceState state) {
- int service = state.getState() == ServiceState.STATE_IN_SERVICE ? 1 : 0;
- int roam = state.getRoaming() ? 1 : 0;
- int stat;
- AtCommandResult result = new AtCommandResult(AtCommandResult.UNSOLICITED);
- mServiceState = state;
- if (service == 0) {
- stat = 0;
- } else {
- stat = (roam == 1) ? 5 : 1;
- }
-
- if (service != mService) {
- mService = service;
- if (sendUpdate) {
- result.addResponse("+CIEV: 1," + mService);
- }
- }
- if (roam != mRoam) {
- mRoam = roam;
- if (sendUpdate) {
- result.addResponse("+CIEV: 6," + mRoam);
- }
- }
- if (stat != mStat) {
- mStat = stat;
- if (sendUpdate) {
- result.addResponse(toCregString());
- }
- }
-
- sendURC(result.toString());
- }
-
- private synchronized void handlePreciseCallStateChange(boolean sendUpdate,
- Connection connection) {
- int call = 0;
- int callsetup = 0;
- int callheld = 0;
- int prevCallsetup = mCallsetup;
- AtCommandResult result = new AtCommandResult(AtCommandResult.UNSOLICITED);
- Call foregroundCall = mCM.getActiveFgCall();
- Call backgroundCall = mCM.getFirstActiveBgCall();
- Call ringingCall = mCM.getFirstActiveRingingCall();
-
- if (VDBG) log("updatePhoneState()");
-
- // This function will get called when the Precise Call State
- // {@link Call.State} changes. Hence, we might get this update
- // even if the {@link PhoneConstants.state} is same as before.
- // Check for the same.
-
- PhoneConstants.State newState = mCM.getState();
- if (newState != mPhoneState) {
- mPhoneState = newState;
- switch (mPhoneState) {
- case IDLE:
- mUserWantsAudio = true; // out of call - reset state
- audioOff();
- break;
- default:
- callStarted();
- }
- }
-
- switch(foregroundCall.getState()) {
- case ACTIVE:
- call = 1;
- mAudioPossible = true;
- break;
- case DIALING:
- callsetup = 2;
- mAudioPossible = true;
- // We also need to send a Call started indication
- // for cases where the 2nd MO was initiated was
- // from a *BT hands free* and is waiting for a
- // +BLND: OK response
- // There is a special case handling of the same case
- // for CDMA below
- if (mCM.getFgPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) {
- callStarted();
- }
- break;
- case ALERTING:
- callsetup = 3;
- // Open the SCO channel for the outgoing call.
- mCallStartTime = System.currentTimeMillis();
- audioOn();
- mAudioPossible = true;
- break;
- case DISCONNECTING:
- // This is a transient state, we don't want to send
- // any AT commands during this state.
- call = mCall;
- callsetup = mCallsetup;
- callheld = mCallheld;
- break;
- default:
- mAudioPossible = false;
- }
-
- switch(ringingCall.getState()) {
- case INCOMING:
- case WAITING:
- callsetup = 1;
- break;
- case DISCONNECTING:
- // This is a transient state, we don't want to send
- // any AT commands during this state.
- call = mCall;
- callsetup = mCallsetup;
- callheld = mCallheld;
- break;
- }
-
- switch(backgroundCall.getState()) {
- case HOLDING:
- if (call == 1) {
- callheld = 1;
- } else {
- call = 1;
- callheld = 2;
- }
- break;
- case DISCONNECTING:
- // This is a transient state, we don't want to send
- // any AT commands during this state.
- call = mCall;
- callsetup = mCallsetup;
- callheld = mCallheld;
- break;
- }
-
- if (mCall != call) {
- if (call == 1) {
- // This means that a call has transitioned from NOT ACTIVE to ACTIVE.
- // Switch on audio.
- mCallStartTime = System.currentTimeMillis();
- audioOn();
- }
- mCall = call;
- if (sendUpdate) {
- result.addResponse("+CIEV: 2," + mCall);
- }
- }
- if (mCallsetup != callsetup) {
- mCallsetup = callsetup;
- if (sendUpdate) {
- // If mCall = 0, send CIEV
- // mCall = 1, mCallsetup = 0, send CIEV
- // mCall = 1, mCallsetup = 1, send CIEV after CCWA,
- // if 3 way supported.
- // mCall = 1, mCallsetup = 2 / 3 -> send CIEV,
- // if 3 way is supported
- if (mCall != 1 || mCallsetup == 0 ||
- mCallsetup != 1 && (mRemoteBrsf & BRSF_HF_CW_THREE_WAY_CALLING) != 0x0) {
- result.addResponse("+CIEV: 3," + mCallsetup);
- }
- }
- }
-
- if (mCM.getDefaultPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
- PhoneApp app = PhoneApp.getInstance();
- if (app.cdmaPhoneCallState != null) {
- CdmaPhoneCallState.PhoneCallState currCdmaThreeWayCallState =
- app.cdmaPhoneCallState.getCurrentCallState();
- CdmaPhoneCallState.PhoneCallState prevCdmaThreeWayCallState =
- app.cdmaPhoneCallState.getPreviousCallState();
-
- log("CDMA call state: " + currCdmaThreeWayCallState + " prev state:" +
- prevCdmaThreeWayCallState);
- callheld = getCdmaCallHeldStatus(currCdmaThreeWayCallState,
- prevCdmaThreeWayCallState);
-
- if (mCdmaThreeWayCallState != currCdmaThreeWayCallState) {
- // In CDMA, the network does not provide any feedback
- // to the phone when the 2nd MO call goes through the
- // stages of DIALING > ALERTING -> ACTIVE we fake the
- // sequence
- if ((currCdmaThreeWayCallState ==
- CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
- && app.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing()) {
- mAudioPossible = true;
- if (sendUpdate) {
- if ((mRemoteBrsf & BRSF_HF_CW_THREE_WAY_CALLING) != 0x0) {
- result.addResponse("+CIEV: 3,2");
- // Mimic putting the call on hold
- result.addResponse("+CIEV: 4,1");
- mCallheld = callheld;
- result.addResponse("+CIEV: 3,3");
- result.addResponse("+CIEV: 3,0");
- }
- }
- // We also need to send a Call started indication
- // for cases where the 2nd MO was initiated was
- // from a *BT hands free* and is waiting for a
- // +BLND: OK response
- callStarted();
- }
-
- // In CDMA, the network does not provide any feedback to
- // the phone when a user merges a 3way call or swaps
- // between two calls we need to send a CIEV response
- // indicating that a call state got changed which should
- // trigger a CLCC update request from the BT client.
- if (currCdmaThreeWayCallState ==
- CdmaPhoneCallState.PhoneCallState.CONF_CALL &&
- prevCdmaThreeWayCallState ==
- CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
- mAudioPossible = true;
- if (sendUpdate) {
- if ((mRemoteBrsf & BRSF_HF_CW_THREE_WAY_CALLING) != 0x0) {
- result.addResponse("+CIEV: 2,1");
- result.addResponse("+CIEV: 3,0");
- }
- }
- }
- }
- mCdmaThreeWayCallState = currCdmaThreeWayCallState;
- }
- }
-
- boolean callsSwitched;
-
- if (mCM.getDefaultPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA &&
- mCdmaThreeWayCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
- callsSwitched = mCdmaCallsSwapped;
- } else {
- callsSwitched =
- (callheld == 1 && ! (backgroundCall.getEarliestConnectTime() ==
- mBgndEarliestConnectionTime));
- mBgndEarliestConnectionTime = backgroundCall.getEarliestConnectTime();
- }
-
-
- if (mCallheld != callheld || callsSwitched) {
- mCallheld = callheld;
- if (sendUpdate) {
- result.addResponse("+CIEV: 4," + mCallheld);
- }
- }
-
- if (callsetup == 1 && callsetup != prevCallsetup) {
- // new incoming call
- String number = null;
- int type = 128;
- // find incoming phone number and type
- if (connection == null) {
- connection = ringingCall.getEarliestConnection();
- if (connection == null) {
- Log.e(TAG, "Could not get a handle on Connection object for new " +
- "incoming call");
- }
- }
- if (connection != null) {
- number = connection.getAddress();
- if (number != null) {
- type = PhoneNumberUtils.toaFromString(number);
- }
- }
- if (number == null) {
- number = "";
- }
- if ((call != 0 || callheld != 0) && sendUpdate) {
- // call waiting
- if ((mRemoteBrsf & BRSF_HF_CW_THREE_WAY_CALLING) != 0x0) {
- result.addResponse("+CCWA: \"" + number + "\"," + type);
- result.addResponse("+CIEV: 3," + callsetup);
- }
- } else {
- // regular new incoming call
- mRingingNumber = number;
- mRingingType = type;
- mIgnoreRing = false;
- mStopRing = false;
-
- if ((mLocalBrsf & BRSF_AG_IN_BAND_RING) != 0x0) {
- mCallStartTime = System.currentTimeMillis();
- audioOn();
- }
- result.addResult(ring());
- }
- }
- sendURC(result.toString());
- }
-
- private int getCdmaCallHeldStatus(CdmaPhoneCallState.PhoneCallState currState,
- CdmaPhoneCallState.PhoneCallState prevState) {
- int callheld;
- // Update the Call held information
- if (currState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
- if (prevState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
- callheld = 0; //0: no calls held, as now *both* the caller are active
- } else {
- callheld = 1; //1: held call and active call, as on answering a
- // Call Waiting, one of the caller *is* put on hold
- }
- } else if (currState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
- callheld = 1; //1: held call and active call, as on make a 3 Way Call
- // the first caller *is* put on hold
- } else {
- callheld = 0; //0: no calls held as this is a SINGLE_ACTIVE call
- }
- return callheld;
- }
-
-
- private AtCommandResult ring() {
- if (sendRingUpdate()) {
- AtCommandResult result = new AtCommandResult(AtCommandResult.UNSOLICITED);
- result.addResponse("RING");
- if (sendClipUpdate()) {
- result.addResponse("+CLIP: \"" + mRingingNumber + "\"," + mRingingType);
- }
-
- Message msg = mStateChangeHandler.obtainMessage(RING);
- mStateChangeHandler.sendMessageDelayed(msg, 3000);
- return result;
- }
- return null;
- }
-
- private synchronized String toCregString() {
- return new String("+CREG: 1," + mStat);
- }
-
- private synchronized void updateCallHeld() {
- if (mCallheld != 0) {
- mCallheld = 0;
- sendURC("+CIEV: 4,0");
- }
- }
-
- private synchronized AtCommandResult toCindResult() {
- AtCommandResult result = new AtCommandResult(AtCommandResult.OK);
- int call, call_setup;
-
- // Handsfree carkits expect that +CIND is properly responded to.
- // Hence we ensure that a proper response is sent for the virtual call too.
- if (isVirtualCallInProgress()) {
- call = 1;
- call_setup = 0;
- } else {
- // regular phone call
- call = mCall;
- call_setup = mCallsetup;
- }
-
- mSignal = asuToSignal(mCM.getDefaultPhone().getSignalStrength());
- String status = "+CIND: " + mService + "," + call + "," + call_setup + "," +
- mCallheld + "," + mSignal + "," + mRoam + "," + mBattchg;
- result.addResponse(status);
- return result;
- }
-
- private synchronized AtCommandResult toCsqResult() {
- AtCommandResult result = new AtCommandResult(AtCommandResult.OK);
- String status = "+CSQ: " + mRssi + ",99";
- result.addResponse(status);
- return result;
- }
-
-
- private synchronized AtCommandResult getCindTestResult() {
- return new AtCommandResult("+CIND: (\"service\",(0-1))," + "(\"call\",(0-1))," +
- "(\"callsetup\",(0-3)),(\"callheld\",(0-2)),(\"signal\",(0-5))," +
- "(\"roam\",(0-1)),(\"battchg\",(0-5))");
- }
-
- private synchronized void ignoreRing() {
- mCallsetup = 0;
- mIgnoreRing = true;
- if (sendUpdate()) {
- sendURC("+CIEV: 3," + mCallsetup);
- }
- }
-
- private void scoClosed() {
- // sync on mUserWantsAudio change
- synchronized(BluetoothHandsfree.this) {
- if (mUserWantsAudio &&
- System.currentTimeMillis() - mCallStartTime < RETRY_SCO_TIME_WINDOW) {
- Message msg = mHandler.obtainMessage(SCO_CONNECTION_CHECK);
- mHandler.sendMessage(msg);
- }
- }
- }
- };
-
- private static final int SCO_CLOSED = 3;
- private static final int CHECK_CALL_STARTED = 4;
- private static final int CHECK_VOICE_RECOGNITION_STARTED = 5;
- private static final int MESSAGE_CHECK_PENDING_SCO = 6;
- private static final int SCO_AUDIO_STATE = 7;
- private static final int SCO_CONNECTION_CHECK = 8;
- private static final int BATTERY_CHANGED = 9;
- private static final int SIGNAL_STRENGTH_CHANGED = 10;
-
- private final class HandsfreeMessageHandler extends Handler {
- private HandsfreeMessageHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case SCO_CLOSED:
- synchronized (BluetoothHandsfree.this) {
- // synchronized
- // Make atomic against audioOn, userWantsAudioOn
- // TODO finer lock to decouple from other call flow such as
- // mWaitingForCallStart change
-
- audioOff();
- // notify mBluetoothPhoneState that the SCO channel has closed
- mBluetoothPhoneState.scoClosed();
- }
- break;
- case CHECK_CALL_STARTED:
- synchronized (BluetoothHandsfree.this) {
- // synchronized
- // Protect test/change of mWaitingForCallStart
- if (mWaitingForCallStart) {
- mWaitingForCallStart = false;
- Log.e(TAG, "Timeout waiting for call to start");
- sendURC("ERROR");
- if (mStartCallWakeLock.isHeld()) {
- mStartCallWakeLock.release();
- }
- }
- }
- break;
- case CHECK_VOICE_RECOGNITION_STARTED:
- synchronized (BluetoothHandsfree.this) {
- // synchronized
- // Protect test/change of mWaitingForVoiceRecognition
- if (mWaitingForVoiceRecognition) {
- mWaitingForVoiceRecognition = false;
- Log.e(TAG, "Timeout waiting for voice recognition to start");
- sendURC("ERROR");
- }
- }
- break;
- case MESSAGE_CHECK_PENDING_SCO:
- synchronized (BluetoothHandsfree.this) {
- // synchronized
- // Protect test/change of mPendingSco
- if (mPendingSco && isA2dpMultiProfile()) {
- Log.w(TAG, "Timeout suspending A2DP for SCO (mA2dpState = " +
- mA2dpState + "). Starting SCO anyway");
- connectScoThread();
- mPendingSco = false;
- }
- }
- break;
- case SCO_AUDIO_STATE:
- BluetoothDevice device = (BluetoothDevice) msg.obj;
- if (getAudioState(device) == BluetoothHeadset.STATE_AUDIO_CONNECTING) {
- setAudioState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, device);
- }
- break;
- case SCO_CONNECTION_CHECK:
- synchronized (mBluetoothPhoneState) {
- // synchronized on mCall change
- if (mBluetoothPhoneState.mCall == 1) {
- // Sometimes, the SCO channel is torn down by HF with no reason.
- // Because we are still in active call, reconnect SCO.
- // audioOn does nothing if the SCO is already on.
- audioOn();
- }
- }
- break;
- case BATTERY_CHANGED:
- mBluetoothPhoneState.updateBatteryState((Intent) msg.obj);
- break;
- case SIGNAL_STRENGTH_CHANGED:
- mBluetoothPhoneState.updateSignalState((Intent) msg.obj);
- break;
- }
- }
- }
-
- private synchronized void setAudioState(int state, BluetoothDevice device) {
- if (VDBG) log("setAudioState(" + state + ")");
- if (mBluetoothHeadset == null) {
- mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEADSET);
- mPendingAudioState = true;
- mAudioState = state;
- return;
- }
- mBluetoothHeadset.setAudioState(device, state);
- }
-
- private synchronized int getAudioState(BluetoothDevice device) {
- if (mBluetoothHeadset == null) return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
- return mBluetoothHeadset.getAudioState(device);
- }
-
- private BluetoothProfile.ServiceListener mProfileListener =
- new BluetoothProfile.ServiceListener() {
- public void onServiceConnected(int profile, BluetoothProfile proxy) {
- if (profile == BluetoothProfile.HEADSET) {
- mBluetoothHeadset = (BluetoothHeadset) proxy;
- synchronized(BluetoothHandsfree.this) {
- if (mPendingAudioState) {
- mBluetoothHeadset.setAudioState(mHeadset.getRemoteDevice(), mAudioState);
- mPendingAudioState = false;
- }
- }
- } else if (profile == BluetoothProfile.A2DP) {
- mA2dp = (BluetoothA2dp) proxy;
- }
- }
- public void onServiceDisconnected(int profile) {
- if (profile == BluetoothProfile.HEADSET) {
- mBluetoothHeadset = null;
- } else if (profile == BluetoothProfile.A2DP) {
- mA2dp = null;
- }
- }
- };
-
- /*
- * Put the AT command, company ID, arguments, and device in an Intent and broadcast it.
- */
- private void broadcastVendorSpecificEventIntent(String command,
- int companyId,
- int commandType,
- Object[] arguments,
- BluetoothDevice device) {
- if (VDBG) log("broadcastVendorSpecificEventIntent(" + command + ")");
- Intent intent =
- new Intent(BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT);
- intent.putExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD, command);
- intent.putExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE,
- commandType);
- // assert: all elements of args are Serializable
- intent.putExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS, arguments);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
-
- intent.addCategory(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY
- + "." + Integer.toString(companyId));
-
- mContext.sendBroadcast(intent, android.Manifest.permission.BLUETOOTH);
- }
-
- void updateBtHandsfreeAfterRadioTechnologyChange() {
- if (VDBG) Log.d(TAG, "updateBtHandsfreeAfterRadioTechnologyChange...");
-
- mBluetoothPhoneState.updateBtPhoneStateAfterRadioTechnologyChange();
- }
-
- /** Request to establish SCO (audio) connection to bluetooth
- * headset/handsfree, if one is connected. Does not block.
- * Returns false if the user has requested audio off, or if there
- * is some other immediate problem that will prevent BT audio.
- */
- /* package */ synchronized boolean audioOn() {
- if (VDBG) log("audioOn()");
- if (!isHeadsetConnected()) {
- if (DBG) log("audioOn(): headset is not connected!");
- return false;
- }
- if (mHeadsetType == TYPE_HANDSFREE && !mServiceConnectionEstablished) {
- if (DBG) log("audioOn(): service connection not yet established!");
- return false;
- }
-
- if (mConnectedSco != null) {
- if (DBG) log("audioOn(): audio is already connected");
- return true;
- }
-
- if (!mUserWantsAudio) {
- if (DBG) log("audioOn(): user requested no audio, ignoring");
- return false;
- }
-
- if (mPendingSco) {
- if (DBG) log("audioOn(): SCO already pending");
- return true;
- }
-
- mA2dpSuspended = false;
- mPendingSco = false;
- if (isA2dpMultiProfile() && mA2dpState == BluetoothA2dp.STATE_PLAYING) {
- if (DBG) log("suspending A2DP stream for SCO");
- mA2dpSuspended = mA2dp.suspendSink(mA2dpDevice);
- if (mA2dpSuspended) {
- mPendingSco = true;
- Message msg = mHandler.obtainMessage(MESSAGE_CHECK_PENDING_SCO);
- mHandler.sendMessageDelayed(msg, 2000);
- } else {
- Log.w(TAG, "Could not suspend A2DP stream for SCO, going ahead with SCO");
- }
- }
-
- if (!mPendingSco) {
- connectScoThread();
- }
-
- return true;
- }
-
- /** Used to indicate the user requested BT audio on.
- * This will establish SCO (BT audio), even if the user requested it off
- * previously on this call.
- */
- /* package */ synchronized void userWantsAudioOn() {
- mUserWantsAudio = true;
- audioOn();
- }
- /** Used to indicate the user requested BT audio off.
- * This will prevent us from establishing BT audio again during this call
- * if audioOn() is called.
- */
- /* package */ synchronized void userWantsAudioOff() {
- mUserWantsAudio = false;
- audioOff();
- }
-
- /** Request to disconnect SCO (audio) connection to bluetooth
- * headset/handsfree, if one is connected. Does not block.
- */
- /* package */ synchronized void audioOff() {
- if (VDBG) log("audioOff(): mPendingSco: " + mPendingSco +
- ", mConnectedSco: " + mConnectedSco +
- ", mA2dpState: " + mA2dpState +
- ", mA2dpSuspended: " + mA2dpSuspended);
-
- if (mA2dpSuspended) {
- if (isA2dpMultiProfile()) {
- if (DBG) log("resuming A2DP stream after disconnecting SCO");
- mA2dp.resumeSink(mA2dpDevice);
- }
- mA2dpSuspended = false;
- }
-
- mPendingSco = false;
-
- if (mSignalScoCloseThread != null) {
- mSignalScoCloseThread.shutdown();
- mSignalScoCloseThread = null;
- }
-
- // Sync with setting mConnectScoThread to null to assure the validity of
- // the condition
- synchronized (ScoSocketConnectThread.class) {
- if (mConnectScoThread != null) {
- mConnectScoThread.shutdown();
- resetConnectScoThread();
- }
- }
-
- closeConnectedSco(); // Should be closed already, but just in case
- }
-
- /* package */ boolean isAudioOn() {
- return (mConnectedSco != null);
- }
-
- private boolean isA2dpMultiProfile() {
- return mA2dp != null && mHeadset != null && mA2dpDevice != null &&
- mA2dpDevice.equals(mHeadset.getRemoteDevice());
- }
-
- /* package */ void ignoreRing() {
- mBluetoothPhoneState.ignoreRing();
- }
-
- private void sendURC(String urc) {
- if (isHeadsetConnected()) {
- mHeadset.sendURC(urc);
- }
- }
-
- /** helper to redial last dialled number */
- private AtCommandResult redial() {
- String number = mPhonebook.getLastDialledNumber();
- if (number == null) {
- // spec seems to suggest sending ERROR if we dont have a
- // number to redial
- if (VDBG) log("Bluetooth redial requested (+BLDN), but no previous " +
- "outgoing calls found. Ignoring");
- return new AtCommandResult(AtCommandResult.ERROR);
- }
- // Outgoing call initiated by the handsfree device
- // Send terminateScoUsingVirtualVoiceCall
- terminateScoUsingVirtualVoiceCall();
- Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
- Uri.fromParts(Constants.SCHEME_TEL, number, null));
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivity(intent);
-
- // We do not immediately respond OK, wait until we get a phone state
- // update. If we return OK now and the handsfree immeidately requests
- // our phone state it will say we are not in call yet which confuses
- // some devices
- expectCallStart();
- return new AtCommandResult(AtCommandResult.UNSOLICITED); // send nothing
- }
-
- /** Build the +CLCC result
- * The complexity arises from the fact that we need to maintain the same
- * CLCC index even as a call moves between states. */
- private synchronized AtCommandResult gsmGetClccResult() {
- // Collect all known connections
- Connection[] clccConnections = new Connection[GSM_MAX_CONNECTIONS]; // indexed by CLCC index
- LinkedList<Connection> newConnections = new LinkedList<Connection>();
- LinkedList<Connection> connections = new LinkedList<Connection>();
-
- Call foregroundCall = mCM.getActiveFgCall();
- Call backgroundCall = mCM.getFirstActiveBgCall();
- Call ringingCall = mCM.getFirstActiveRingingCall();
-
- if (ringingCall.getState().isAlive()) {
- connections.addAll(ringingCall.getConnections());
- }
- if (foregroundCall.getState().isAlive()) {
- connections.addAll(foregroundCall.getConnections());
- }
- if (backgroundCall.getState().isAlive()) {
- connections.addAll(backgroundCall.getConnections());
- }
-
- // Mark connections that we already known about
- boolean clccUsed[] = new boolean[GSM_MAX_CONNECTIONS];
- for (int i = 0; i < GSM_MAX_CONNECTIONS; i++) {
- clccUsed[i] = mClccUsed[i];
- mClccUsed[i] = false;
- }
- for (Connection c : connections) {
- boolean found = false;
- long timestamp = c.getCreateTime();
- for (int i = 0; i < GSM_MAX_CONNECTIONS; i++) {
- if (clccUsed[i] && timestamp == mClccTimestamps[i]) {
- mClccUsed[i] = true;
- found = true;
- clccConnections[i] = c;
- break;
- }
- }
- if (!found) {
- newConnections.add(c);
- }
- }
-
- // Find a CLCC index for new connections
- while (!newConnections.isEmpty()) {
- // Find lowest empty index
- int i = 0;
- while (mClccUsed[i]) i++;
- // Find earliest connection
- long earliestTimestamp = newConnections.get(0).getCreateTime();
- Connection earliestConnection = newConnections.get(0);
- for (int j = 0; j < newConnections.size(); j++) {
- long timestamp = newConnections.get(j).getCreateTime();
- if (timestamp < earliestTimestamp) {
- earliestTimestamp = timestamp;
- earliestConnection = newConnections.get(j);
- }
- }
-
- // update
- mClccUsed[i] = true;
- mClccTimestamps[i] = earliestTimestamp;
- clccConnections[i] = earliestConnection;
- newConnections.remove(earliestConnection);
- }
-
- // Build CLCC
- AtCommandResult result = new AtCommandResult(AtCommandResult.OK);
- for (int i = 0; i < clccConnections.length; i++) {
- if (mClccUsed[i]) {
- String clccEntry = connectionToClccEntry(i, clccConnections[i]);
- if (clccEntry != null) {
- result.addResponse(clccEntry);
- }
- }
- }
-
- return result;
- }
-
- /** Convert a Connection object into a single +CLCC result */
- private String connectionToClccEntry(int index, Connection c) {
- int state;
- switch (c.getState()) {
- case ACTIVE:
- state = 0;
- break;
- case HOLDING:
- state = 1;
- break;
- case DIALING:
- state = 2;
- break;
- case ALERTING:
- state = 3;
- break;
- case INCOMING:
- state = 4;
- break;
- case WAITING:
- state = 5;
- break;
- default:
- return null; // bad state
- }
-
- int mpty = 0;
- Call call = c.getCall();
- if (call != null) {
- mpty = call.isMultiparty() ? 1 : 0;
- }
-
- int direction = c.isIncoming() ? 1 : 0;
-
- String number = c.getAddress();
- int type = -1;
- if (number != null) {
- type = PhoneNumberUtils.toaFromString(number);
- }
-
- String result = "+CLCC: " + (index + 1) + "," + direction + "," + state + ",0," + mpty;
- if (number != null) {
- result += ",\"" + number + "\"," + type;
- }
- return result;
- }
-
- /** Build the +CLCC result for CDMA
- * The complexity arises from the fact that we need to maintain the same
- * CLCC index even as a call moves between states. */
- private synchronized AtCommandResult cdmaGetClccResult() {
- // In CDMA at one time a user can have only two live/active connections
- Connection[] clccConnections = new Connection[CDMA_MAX_CONNECTIONS];// indexed by CLCC index
- Call foregroundCall = mCM.getActiveFgCall();
- Call ringingCall = mCM.getFirstActiveRingingCall();
-
- Call.State ringingCallState = ringingCall.getState();
- // If the Ringing Call state is INCOMING, that means this is the very first call
- // hence there should not be any Foreground Call
- if (ringingCallState == Call.State.INCOMING) {
- if (VDBG) log("Filling clccConnections[0] for INCOMING state");
- clccConnections[0] = ringingCall.getLatestConnection();
- } else if (foregroundCall.getState().isAlive()) {
- // Getting Foreground Call connection based on Call state
- if (ringingCall.isRinging()) {
- if (VDBG) log("Filling clccConnections[0] & [1] for CALL WAITING state");
- clccConnections[0] = foregroundCall.getEarliestConnection();
- clccConnections[1] = ringingCall.getLatestConnection();
- } else {
- if (foregroundCall.getConnections().size() <= 1) {
- // Single call scenario
- if (VDBG) log("Filling clccConnections[0] with ForgroundCall latest connection");
- clccConnections[0] = foregroundCall.getLatestConnection();
- } else {
- // Multiple Call scenario. This would be true for both
- // CONF_CALL and THRWAY_ACTIVE state
- if (VDBG) log("Filling clccConnections[0] & [1] with ForgroundCall connections");
- clccConnections[0] = foregroundCall.getEarliestConnection();
- clccConnections[1] = foregroundCall.getLatestConnection();
- }
- }
- }
-
- // Update the mCdmaIsSecondCallActive flag based on the Phone call state
- if (PhoneApp.getInstance().cdmaPhoneCallState.getCurrentCallState()
- == CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE) {
- cdmaSetSecondCallState(false);
- } else if (PhoneApp.getInstance().cdmaPhoneCallState.getCurrentCallState()
- == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
- cdmaSetSecondCallState(true);
- }
-
- // Build CLCC
- AtCommandResult result = new AtCommandResult(AtCommandResult.OK);
- for (int i = 0; (i < clccConnections.length) && (clccConnections[i] != null); i++) {
- String clccEntry = cdmaConnectionToClccEntry(i, clccConnections[i]);
- if (clccEntry != null) {
- result.addResponse(clccEntry);
- }
- }
-
- return result;
- }
-
- /** Convert a Connection object into a single +CLCC result for CDMA phones */
- private String cdmaConnectionToClccEntry(int index, Connection c) {
- int state;
- PhoneApp app = PhoneApp.getInstance();
- CdmaPhoneCallState.PhoneCallState currCdmaCallState =
- app.cdmaPhoneCallState.getCurrentCallState();
- CdmaPhoneCallState.PhoneCallState prevCdmaCallState =
- app.cdmaPhoneCallState.getPreviousCallState();
-
- if ((prevCdmaCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
- && (currCdmaCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL)) {
- // If the current state is reached after merging two calls
- // we set the state of all the connections as ACTIVE
- state = 0;
- } else {
- switch (c.getState()) {
- case ACTIVE:
- // For CDMA since both the connections are set as active by FW after accepting
- // a Call waiting or making a 3 way call, we need to set the state specifically
- // to ACTIVE/HOLDING based on the mCdmaIsSecondCallActive flag. This way the
- // CLCC result will allow BT devices to enable the swap or merge options
- if (index == 0) { // For the 1st active connection
- state = mCdmaIsSecondCallActive ? 1 : 0;
- } else { // for the 2nd active connection
- state = mCdmaIsSecondCallActive ? 0 : 1;
- }
- break;
- case HOLDING:
- state = 1;
- break;
- case DIALING:
- state = 2;
- break;
- case ALERTING:
- state = 3;
- break;
- case INCOMING:
- state = 4;
- break;
- case WAITING:
- state = 5;
- break;
- default:
- return null; // bad state
- }
- }
-
- int mpty = 0;
- if (currCdmaCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
- if (prevCdmaCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
- // If the current state is reached after merging two calls
- // we set the multiparty call true.
- mpty = 1;
- } else {
- // CALL_CONF state is not from merging two calls, but from
- // accepting the second call. In this case first will be on
- // hold in most cases but in some cases its already merged.
- // However, we will follow the common case and the test case
- // as per Bluetooth SIG PTS
- mpty = 0;
- }
- } else {
- mpty = 0;
- }
-
- int direction = c.isIncoming() ? 1 : 0;
-
- String number = c.getAddress();
- int type = -1;
- if (number != null) {
- type = PhoneNumberUtils.toaFromString(number);
- }
-
- String result = "+CLCC: " + (index + 1) + "," + direction + "," + state + ",0," + mpty;
- if (number != null) {
- result += ",\"" + number + "\"," + type;
- }
- return result;
- }
-
- /*
- * Register a vendor-specific command.
- * @param commandName the name of the command. For example, if the expected
- * incoming command is <code>AT+FOO=bar,baz</code>, the value of this should be
- * <code>"+FOO"</code>.
- * @param companyId the Bluetooth SIG Company Identifier
- * @param parser the AtParser on which to register the command
- */
- private void registerVendorSpecificCommand(String commandName,
- int companyId,
- AtParser parser) {
- parser.register(commandName,
- new VendorSpecificCommandHandler(commandName, companyId));
- }
-
- /*
- * Register all vendor-specific commands here.
- */
- private void registerAllVendorSpecificCommands() {
- AtParser parser = mHeadset.getAtParser();
-
- // Plantronics-specific headset events go here
- registerVendorSpecificCommand("+XEVENT",
- BluetoothAssignedNumbers.PLANTRONICS,
- parser);
- }
-
- /**
- * Register AT Command handlers to implement the Headset profile
- */
- private void initializeHeadsetAtParser() {
- if (VDBG) log("Registering Headset AT commands");
- AtParser parser = mHeadset.getAtParser();
- // Headsets usually only have one button, which is meant to cause the
- // HS to send us AT+CKPD=200 or AT+CKPD.
- parser.register("+CKPD", new AtCommandHandler() {
- private AtCommandResult headsetButtonPress() {
- if (mCM.getFirstActiveRingingCall().isRinging()) {
- // Answer the call
- mBluetoothPhoneState.stopRing();
- sendURC("OK");
- PhoneUtils.answerCall(mCM.getFirstActiveRingingCall());
- // If in-band ring tone is supported, SCO connection will already
- // be up and the following call will just return.
- audioOn();
- return new AtCommandResult(AtCommandResult.UNSOLICITED);
- } else if (mCM.hasActiveFgCall()) {
- if (!isAudioOn()) {
- // Transfer audio from AG to HS
- audioOn();
- } else {
- if (mHeadset.getDirection() == HeadsetBase.DIRECTION_INCOMING &&
- (System.currentTimeMillis() - mHeadset.getConnectTimestamp()) < 5000) {
- // Headset made a recent ACL connection to us - and
- // made a mandatory AT+CKPD request to connect
- // audio which races with our automatic audio
- // setup. ignore
- } else {
- // Hang up the call
- audioOff();
- PhoneUtils.hangup(PhoneApp.getInstance().mCM);
- }
- }
- return new AtCommandResult(AtCommandResult.OK);
- } else {
- // No current call - redial last number
- return redial();
- }
- }
- @Override
- public AtCommandResult handleActionCommand() {
- return headsetButtonPress();
- }
- @Override
- public AtCommandResult handleSetCommand(Object[] args) {
- return headsetButtonPress();
- }
- });
- }
-
- /**
- * Register AT Command handlers to implement the Handsfree profile
- */
- private void initializeHandsfreeAtParser() {
- if (VDBG) log("Registering Handsfree AT commands");
- AtParser parser = mHeadset.getAtParser();
- final Phone phone = mCM.getDefaultPhone();
-
- // Answer
- parser.register('A', new AtCommandHandler() {
- @Override
- public AtCommandResult handleBasicCommand(String args) {
- sendURC("OK");
- mBluetoothPhoneState.stopRing();
- PhoneUtils.answerCall(mCM.getFirstActiveRingingCall());
- return new AtCommandResult(AtCommandResult.UNSOLICITED);
- }
- });
- parser.register('D', new AtCommandHandler() {
- @Override
- public AtCommandResult handleBasicCommand(String args) {
- if (args.length() > 0) {
- if (args.charAt(0) == '>') {
- // Yuck - memory dialling requested.
- // Just dial last number for now
- if (args.startsWith(">9999")) { // for PTS test
- return new AtCommandResult(AtCommandResult.ERROR);
- }
- return redial();
- } else {
- // Send terminateScoUsingVirtualVoiceCall
- terminateScoUsingVirtualVoiceCall();
- // Remove trailing ';'
- if (args.charAt(args.length() - 1) == ';') {
- args = args.substring(0, args.length() - 1);
- }
-
- args = PhoneNumberUtils.convertPreDial(args);
-
- Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
- Uri.fromParts(Constants.SCHEME_TEL, args, null));
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivity(intent);
-
- expectCallStart();
- return new AtCommandResult(AtCommandResult.UNSOLICITED); // send nothing
- }
- }
- return new AtCommandResult(AtCommandResult.ERROR);
- }
- });
-
- // Hang-up command
- parser.register("+CHUP", new AtCommandHandler() {
- @Override
- public AtCommandResult handleActionCommand() {
- sendURC("OK");
- if (isVirtualCallInProgress()) {
- terminateScoUsingVirtualVoiceCall();
- } else {
- if (mCM.hasActiveFgCall()) {
- PhoneUtils.hangupActiveCall(mCM.getActiveFgCall());
- } else if (mCM.hasActiveRingingCall()) {
- PhoneUtils.hangupRingingCall(mCM.getFirstActiveRingingCall());
- } else if (mCM.hasActiveBgCall()) {
- PhoneUtils.hangupHoldingCall(mCM.getFirstActiveBgCall());
- }
- }
- return new AtCommandResult(AtCommandResult.UNSOLICITED);
- }
- });
-
- // Bluetooth Retrieve Supported Features command
- parser.register("+BRSF", new AtCommandHandler() {
- private AtCommandResult sendBRSF() {
- return new AtCommandResult("+BRSF: " + mLocalBrsf);
- }
- @Override
- public AtCommandResult handleSetCommand(Object[] args) {
- // AT+BRSF=<handsfree supported features bitmap>
- // Handsfree is telling us which features it supports. We
- // send the features we support
- if (args.length == 1 && (args[0] instanceof Integer)) {
- mRemoteBrsf = (Integer) args[0];
- } else {
- Log.w(TAG, "HF didn't sent BRSF assuming 0");
- }
- return sendBRSF();
- }
- @Override
- public AtCommandResult handleActionCommand() {
- // This seems to be out of spec, but lets do the nice thing
- return sendBRSF();
- }
- @Override
- public AtCommandResult handleReadCommand() {
- // This seems to be out of spec, but lets do the nice thing
- return sendBRSF();
- }
- });
-
- // Call waiting notification on/off
- parser.register("+CCWA", new AtCommandHandler() {
- @Override
- public AtCommandResult handleActionCommand() {
- // Seems to be out of spec, but lets return nicely
- return new AtCommandResult(AtCommandResult.OK);
- }
- @Override
- public AtCommandResult handleReadCommand() {
- // Call waiting is always on
- return new AtCommandResult("+CCWA: 1");
- }
- @Override
- public AtCommandResult handleSetCommand(Object[] args) {
- // AT+CCWA=<n>
- // Handsfree is trying to enable/disable call waiting. We
- // cannot disable in the current implementation.
- return new AtCommandResult(AtCommandResult.OK);
- }
- @Override
- public AtCommandResult handleTestCommand() {
- // Request for range of supported CCWA paramters
- return new AtCommandResult("+CCWA: (\"n\",(1))");
- }
- });
-
- // Mobile Equipment Event Reporting enable/disable command
- // Of the full 3GPP syntax paramters (mode, keyp, disp, ind, bfr) we
- // only support paramter ind (disable/enable evert reporting using
- // +CDEV)
- parser.register("+CMER", new AtCommandHandler() {
- @Override
- public AtCommandResult handleReadCommand() {
- return new AtCommandResult(
- "+CMER: 3,0,0," + (mIndicatorsEnabled ? "1" : "0"));
- }
- @Override
- public AtCommandResult handleSetCommand(Object[] args) {
- if (args.length < 4) {
- // This is a syntax error
- return new AtCommandResult(AtCommandResult.ERROR);
- } else if (args[0].equals(3) && args[1].equals(0) &&
- args[2].equals(0)) {
- boolean valid = false;
- if (args[3].equals(0)) {
- mIndicatorsEnabled = false;
- valid = true;
- } else if (args[3].equals(1)) {
- mIndicatorsEnabled = true;
- valid = true;
- }
- if (valid) {
- if ((mRemoteBrsf & BRSF_HF_CW_THREE_WAY_CALLING) == 0x0) {
- mServiceConnectionEstablished = true;
- sendURC("OK"); // send immediately, then initiate audio
- if (isIncallAudio()) {
- audioOn();
- } else if (mCM.getFirstActiveRingingCall().isRinging()) {
- // need to update HS with RING cmd when single
- // ringing call exist
- mBluetoothPhoneState.ring();
- }
- // only send OK once
- return new AtCommandResult(AtCommandResult.UNSOLICITED);
- } else {
- return new AtCommandResult(AtCommandResult.OK);
- }
- }
- }
- return reportCmeError(BluetoothCmeError.OPERATION_NOT_SUPPORTED);
- }
- @Override
- public AtCommandResult handleTestCommand() {
- return new AtCommandResult("+CMER: (3),(0),(0),(0-1)");
- }
- });
-
- // Mobile Equipment Error Reporting enable/disable
- parser.register("+CMEE", new AtCommandHandler() {
- @Override
- public AtCommandResult handleActionCommand() {
- // out of spec, assume they want to enable
- mCmee = true;
- return new AtCommandResult(AtCommandResult.OK);
- }
- @Override
- public AtCommandResult handleReadCommand() {
- return new AtCommandResult("+CMEE: " + (mCmee ? "1" : "0"));
- }
- @Override
- public AtCommandResult handleSetCommand(Object[] args) {
- // AT+CMEE=<n>
- if (args.length == 0) {
- // <n> ommitted - default to 0
- mCmee = false;
- return new AtCommandResult(AtCommandResult.OK);
- } else if (!(args[0] instanceof Integer)) {
- // Syntax error
- return new AtCommandResult(AtCommandResult.ERROR);
- } else {
- mCmee = ((Integer)args[0] == 1);
- return new AtCommandResult(AtCommandResult.OK);
- }
- }
- @Override
- public AtCommandResult handleTestCommand() {
- // Probably not required but spec, but no harm done
- return new AtCommandResult("+CMEE: (0-1)");
- }
- });
-
- // Bluetooth Last Dialled Number
- parser.register("+BLDN", new AtCommandHandler() {
- @Override
- public AtCommandResult handleActionCommand() {
- return redial();
- }
- });
-
- // Indicator Update command
- parser.register("+CIND", new AtCommandHandler() {
- @Override
- public AtCommandResult handleReadCommand() {
- return mBluetoothPhoneState.toCindResult();
- }
- @Override
- public AtCommandResult handleTestCommand() {
- return mBluetoothPhoneState.getCindTestResult();
- }
- });
-
- // Query Signal Quality (legacy)
- parser.register("+CSQ", new AtCommandHandler() {
- @Override
- public AtCommandResult handleActionCommand() {
- return mBluetoothPhoneState.toCsqResult();
- }
- });
-
- // Query network registration state
- parser.register("+CREG", new AtCommandHandler() {
- @Override
- public AtCommandResult handleReadCommand() {
- return new AtCommandResult(mBluetoothPhoneState.toCregString());
- }
- });
-
- // Send DTMF. I don't know if we are also expected to play the DTMF tone
- // locally, right now we don't
- parser.register("+VTS", new AtCommandHandler() {
- @Override
- public AtCommandResult handleSetCommand(Object[] args) {
- if (args.length >= 1) {
- char c;
- if (args[0] instanceof Integer) {
- c = ((Integer) args[0]).toString().charAt(0);
- } else {
- c = ((String) args[0]).charAt(0);
- }
- if (isValidDtmf(c)) {
- phone.sendDtmf(c);
- return new AtCommandResult(AtCommandResult.OK);
- }
- }
- return new AtCommandResult(AtCommandResult.ERROR);
- }
- private boolean isValidDtmf(char c) {
- switch (c) {
- case '#':
- case '*':
- return true;
- default:
- if (Character.digit(c, 14) != -1) {
- return true; // 0-9 and A-D
- }
- return false;
- }
- }
- });
-
- // List calls
- parser.register("+CLCC", new AtCommandHandler() {
- @Override
- public AtCommandResult handleActionCommand() {
- int phoneType = phone.getPhoneType();
- // Handsfree carkits expect that +CLCC is properly responded to.
- // Hence we ensure that a proper response is sent for the virtual call too.
- if (isVirtualCallInProgress()) {
- String number = phone.getLine1Number();
- AtCommandResult result = new AtCommandResult(AtCommandResult.OK);
- String args;
- if (number == null) {
- args = "+CLCC: 1,0,0,0,0,\"\",0";
- }
- else
- {
- args = "+CLCC: 1,0,0,0,0,\"" + number + "\"," +
- PhoneNumberUtils.toaFromString(number);
- }
- result.addResponse(args);
- return result;
- }
- if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
- return cdmaGetClccResult();
- } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
- return gsmGetClccResult();
- } else {
- throw new IllegalStateException("Unexpected phone type: " + phoneType);
- }
- }
- });
-
- // Call Hold and Multiparty Handling command
- parser.register("+CHLD", new AtCommandHandler() {
- @Override
- public AtCommandResult handleSetCommand(Object[] args) {
- int phoneType = phone.getPhoneType();
- Call ringingCall = mCM.getFirstActiveRingingCall();
- Call backgroundCall = mCM.getFirstActiveBgCall();
-
- if (args.length >= 1) {
- if (args[0].equals(0)) {
- boolean result;
- if (ringingCall.isRinging()) {
- result = PhoneUtils.hangupRingingCall(ringingCall);
- } else {
- result = PhoneUtils.hangupHoldingCall(backgroundCall);
- }
- if (result) {
- return new AtCommandResult(AtCommandResult.OK);
- } else {
- return new AtCommandResult(AtCommandResult.ERROR);
- }
- } else if (args[0].equals(1)) {
- if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
- if (ringingCall.isRinging()) {
- // Hangup the active call and then answer call waiting call.
- if (VDBG) log("CHLD:1 Callwaiting Answer call");
- PhoneUtils.hangupRingingAndActive(phone);
- } else {
- // If there is no Call waiting then just hangup
- // the active call. In CDMA this mean that the complete
- // call session would be ended
- if (VDBG) log("CHLD:1 Hangup Call");
- PhoneUtils.hangup(PhoneApp.getInstance().mCM);
- }
- return new AtCommandResult(AtCommandResult.OK);
- } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
- // Hangup active call, answer held call
- if (PhoneUtils.answerAndEndActive(
- PhoneApp.getInstance().mCM, ringingCall)) {
- return new AtCommandResult(AtCommandResult.OK);
- } else {
- return new AtCommandResult(AtCommandResult.ERROR);
- }
- } else {
- throw new IllegalStateException("Unexpected phone type: " + phoneType);
- }
- } else if (args[0].equals(2)) {
- sendURC("OK");
- if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
- // For CDMA, the way we switch to a new incoming call is by
- // calling PhoneUtils.answerCall(). switchAndHoldActive() won't
- // properly update the call state within telephony.
- // If the Phone state is already in CONF_CALL then we simply send
- // a flash cmd by calling switchHoldingAndActive()
- if (ringingCall.isRinging()) {
- if (VDBG) log("CHLD:2 Callwaiting Answer call");
- PhoneUtils.answerCall(ringingCall);
- PhoneUtils.setMute(false);
- // Setting the second callers state flag to TRUE (i.e. active)
- cdmaSetSecondCallState(true);
- } else if (PhoneApp.getInstance().cdmaPhoneCallState
- .getCurrentCallState()
- == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
- if (VDBG) log("CHLD:2 Swap Calls");
- PhoneUtils.switchHoldingAndActive(backgroundCall);
- // Toggle the second callers active state flag
- cdmaSwapSecondCallState();
- }
- } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
- PhoneUtils.switchHoldingAndActive(backgroundCall);
- } else {
- throw new IllegalStateException("Unexpected phone type: " + phoneType);
- }
- return new AtCommandResult(AtCommandResult.UNSOLICITED);
- } else if (args[0].equals(3)) {
- sendURC("OK");
- if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
- CdmaPhoneCallState.PhoneCallState state =
- PhoneApp.getInstance().cdmaPhoneCallState.getCurrentCallState();
- // For CDMA, we need to check if the call is in THRWAY_ACTIVE state
- if (state == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
- if (VDBG) log("CHLD:3 Merge Calls");
- PhoneUtils.mergeCalls();
- } else if (state == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
- // State is CONF_CALL already and we are getting a merge call
- // This can happen when CONF_CALL was entered from a Call Waiting
- mBluetoothPhoneState.updateCallHeld();
- }
- } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
- if (mCM.hasActiveFgCall() && mCM.hasActiveBgCall()) {
- PhoneUtils.mergeCalls();
- }
- } else {
- throw new IllegalStateException("Unexpected phone type: " + phoneType);
- }
- return new AtCommandResult(AtCommandResult.UNSOLICITED);
- }
- }
- return new AtCommandResult(AtCommandResult.ERROR);
- }
- @Override
- public AtCommandResult handleTestCommand() {
- mServiceConnectionEstablished = true;
- sendURC("+CHLD: (0,1,2,3)");
- sendURC("OK"); // send reply first, then connect audio
- if (isIncallAudio()) {
- audioOn();
- } else if (mCM.getFirstActiveRingingCall().isRinging()) {
- // need to update HS with RING when single ringing call exist
- mBluetoothPhoneState.ring();
- }
- // already replied
- return new AtCommandResult(AtCommandResult.UNSOLICITED);
- }
- });
-
- // Get Network operator name
- parser.register("+COPS", new AtCommandHandler() {
- @Override
- public AtCommandResult handleReadCommand() {
- String operatorName = phone.getServiceState().getOperatorAlphaLong();
- if (operatorName != null) {
- if (operatorName.length() > 16) {
- operatorName = operatorName.substring(0, 16);
- }
- return new AtCommandResult(
- "+COPS: 0,0,\"" + operatorName + "\"");
- } else {
- return new AtCommandResult(
- "+COPS: 0");
- }
- }
- @Override
- public AtCommandResult handleSetCommand(Object[] args) {
- // Handsfree only supports AT+COPS=3,0
- if (args.length != 2 || !(args[0] instanceof Integer)
- || !(args[1] instanceof Integer)) {
- // syntax error
- return new AtCommandResult(AtCommandResult.ERROR);
- } else if ((Integer)args[0] != 3 || (Integer)args[1] != 0) {
- return reportCmeError(BluetoothCmeError.OPERATION_NOT_SUPPORTED);
- } else {
- return new AtCommandResult(AtCommandResult.OK);
- }
- }
- @Override
- public AtCommandResult handleTestCommand() {
- // Out of spec, but lets be friendly
- return new AtCommandResult("+COPS: (3),(0)");
- }
- });
-
- // Mobile PIN
- // AT+CPIN is not in the handsfree spec (although it is in 3GPP)
- parser.register("+CPIN", new AtCommandHandler() {
- @Override
- public AtCommandResult handleReadCommand() {
- return new AtCommandResult("+CPIN: READY");
- }
- });
-
- // Bluetooth Response and Hold
- // Only supported on PDC (Japan) and CDMA networks.
- parser.register("+BTRH", new AtCommandHandler() {
- @Override
- public AtCommandResult handleReadCommand() {
- // Replying with just OK indicates no response and hold
- // features in use now
- return new AtCommandResult(AtCommandResult.OK);
- }
- @Override
- public AtCommandResult handleSetCommand(Object[] args) {
- // Neeed PDC or CDMA
- return new AtCommandResult(AtCommandResult.ERROR);
- }
- });
-
- // Request International Mobile Subscriber Identity (IMSI)
- // Not in bluetooth handset spec
- parser.register("+CIMI", new AtCommandHandler() {
- @Override
- public AtCommandResult handleActionCommand() {
- // AT+CIMI
- String imsi = phone.getSubscriberId();
- if (imsi == null || imsi.length() == 0) {
- return reportCmeError(BluetoothCmeError.SIM_FAILURE);
- } else {
- return new AtCommandResult(imsi);
- }
- }
- });
-
- // Calling Line Identification Presentation
- parser.register("+CLIP", new AtCommandHandler() {
- @Override
- public AtCommandResult handleReadCommand() {
- // Currently assumes the network is provisioned for CLIP
- return new AtCommandResult("+CLIP: " + (mClip ? "1" : "0") + ",1");
- }
- @Override
- public AtCommandResult handleSetCommand(Object[] args) {
- // AT+CLIP=<n>
- if (args.length >= 1 && (args[0].equals(0) || args[0].equals(1))) {
- mClip = args[0].equals(1);
- return new AtCommandResult(AtCommandResult.OK);
- } else {
- return new AtCommandResult(AtCommandResult.ERROR);
- }
- }
- @Override
- public AtCommandResult handleTestCommand() {
- return new AtCommandResult("+CLIP: (0-1)");
- }
- });
-
- // AT+CGSN - Returns the device IMEI number.
- parser.register("+CGSN", new AtCommandHandler() {
- @Override
- public AtCommandResult handleActionCommand() {
- // Get the IMEI of the device.
- // phone will not be NULL at this point.
- return new AtCommandResult("+CGSN: " + phone.getDeviceId());
- }
- });
-
- // AT+CGMM - Query Model Information
- parser.register("+CGMM", new AtCommandHandler() {
- @Override
- public AtCommandResult handleActionCommand() {
- // Return the Model Information.
- String model = SystemProperties.get("ro.product.model");
- if (model != null) {
- return new AtCommandResult("+CGMM: " + model);
- } else {
- return new AtCommandResult(AtCommandResult.ERROR);
- }
- }
- });
-
- // AT+CGMI - Query Manufacturer Information
- parser.register("+CGMI", new AtCommandHandler() {
- @Override
- public AtCommandResult handleActionCommand() {
- // Return the Model Information.
- String manuf = SystemProperties.get("ro.product.manufacturer");
- if (manuf != null) {
- return new AtCommandResult("+CGMI: " + manuf);
- } else {
- return new AtCommandResult(AtCommandResult.ERROR);
- }
- }
- });
-
- // Noise Reduction and Echo Cancellation control
- parser.register("+NREC", new AtCommandHandler() {
- @Override
- public AtCommandResult handleSetCommand(Object[] args) {
- if (args[0].equals(0)) {
- mAudioManager.setParameters(HEADSET_NREC+"=off");
- return new AtCommandResult(AtCommandResult.OK);
- } else if (args[0].equals(1)) {
- mAudioManager.setParameters(HEADSET_NREC+"=on");
- return new AtCommandResult(AtCommandResult.OK);
- }
- return new AtCommandResult(AtCommandResult.ERROR);
- }
- });
-
- // Voice recognition (dialing)
- parser.register("+BVRA", new AtCommandHandler() {
- @Override
- public AtCommandResult handleSetCommand(Object[] args) {
- if (!BluetoothHeadset.isBluetoothVoiceDialingEnabled(mContext)) {
- return new AtCommandResult(AtCommandResult.ERROR);
- }
- if (args.length >= 1 && args[0].equals(1)) {
- synchronized (BluetoothHandsfree.this) {
- if (!isVoiceRecognitionInProgress() &&
- !isCellularCallInProgress() &&
- !isVirtualCallInProgress()) {
- try {
- mContext.startActivity(sVoiceCommandIntent);
- } catch (ActivityNotFoundException e) {
- return new AtCommandResult(AtCommandResult.ERROR);
- }
- expectVoiceRecognition();
- }
- }
- return new AtCommandResult(AtCommandResult.UNSOLICITED); // send nothing yet
- } else if (args.length >= 1 && args[0].equals(0)) {
- if (isVoiceRecognitionInProgress()) {
- audioOff();
- }
- return new AtCommandResult(AtCommandResult.OK);
- }
- return new AtCommandResult(AtCommandResult.ERROR);
- }
- @Override
- public AtCommandResult handleTestCommand() {
- return new AtCommandResult("+BVRA: (0-1)");
- }
- });
-
- // Retrieve Subscriber Number
- parser.register("+CNUM", new AtCommandHandler() {
- @Override
- public AtCommandResult handleActionCommand() {
- String number = phone.getLine1Number();
- if (number == null) {
- return new AtCommandResult(AtCommandResult.OK);
- }
- return new AtCommandResult("+CNUM: ,\"" + number + "\"," +
- PhoneNumberUtils.toaFromString(number) + ",,4");
- }
- });
-
- // Microphone Gain
- parser.register("+VGM", new AtCommandHandler() {
- @Override
- public AtCommandResult handleSetCommand(Object[] args) {
- // AT+VGM=<gain> in range [0,15]
- // Headset/Handsfree is reporting its current gain setting
- return new AtCommandResult(AtCommandResult.OK);
- }
- });
-
- // Speaker Gain
- parser.register("+VGS", new AtCommandHandler() {
- @Override
- public AtCommandResult handleSetCommand(Object[] args) {
- // AT+VGS=<gain> in range [0,15]
- if (args.length != 1 || !(args[0] instanceof Integer)) {
- return new AtCommandResult(AtCommandResult.ERROR);
- }
- mScoGain = (Integer) args[0];
- int flag = mAudioManager.isBluetoothScoOn() ? AudioManager.FLAG_SHOW_UI:0;
-
- mAudioManager.setStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO, mScoGain, flag);
- return new AtCommandResult(AtCommandResult.OK);
- }
- });
-
- // Phone activity status
- parser.register("+CPAS", new AtCommandHandler() {
- @Override
- public AtCommandResult handleActionCommand() {
- int status = 0;
- switch (mCM.getState()) {
- case IDLE:
- status = 0;
- break;
- case RINGING:
- status = 3;
- break;
- case OFFHOOK:
- status = 4;
- break;
- }
- return new AtCommandResult("+CPAS: " + status);
- }
- });
-
- mPhonebook.register(parser);
- }
-
- public void sendScoGainUpdate(int gain) {
- if (mScoGain != gain && (mRemoteBrsf & BRSF_HF_REMOTE_VOL_CONTROL) != 0x0) {
- sendURC("+VGS:" + gain);
- mScoGain = gain;
- }
- }
-
- public AtCommandResult reportCmeError(int error) {
- if (mCmee) {
- AtCommandResult result = new AtCommandResult(AtCommandResult.UNSOLICITED);
- result.addResponse("+CME ERROR: " + error);
- return result;
- } else {
- return new AtCommandResult(AtCommandResult.ERROR);
- }
- }
-
- private static final int START_CALL_TIMEOUT = 10000; // ms
-
- private synchronized void expectCallStart() {
- mWaitingForCallStart = true;
- Message msg = Message.obtain(mHandler, CHECK_CALL_STARTED);
- mHandler.sendMessageDelayed(msg, START_CALL_TIMEOUT);
- if (!mStartCallWakeLock.isHeld()) {
- mStartCallWakeLock.acquire(START_CALL_TIMEOUT);
- }
- }
-
- private synchronized void callStarted() {
- if (mWaitingForCallStart) {
- mWaitingForCallStart = false;
- sendURC("OK");
- if (mStartCallWakeLock.isHeld()) {
- mStartCallWakeLock.release();
- }
- }
- }
-
- private static final int START_VOICE_RECOGNITION_TIMEOUT = 5000; // ms
-
- private synchronized void expectVoiceRecognition() {
- mWaitingForVoiceRecognition = true;
- Message msg = Message.obtain(mHandler, CHECK_VOICE_RECOGNITION_STARTED);
- mHandler.sendMessageDelayed(msg, START_VOICE_RECOGNITION_TIMEOUT);
- if (!mStartVoiceRecognitionWakeLock.isHeld()) {
- mStartVoiceRecognitionWakeLock.acquire(START_VOICE_RECOGNITION_TIMEOUT);
- }
- }
-
- /* package */ synchronized boolean startVoiceRecognition() {
-
- if ((isCellularCallInProgress()) ||
- (isVirtualCallInProgress()) ||
- mVoiceRecognitionStarted) {
- Log.e(TAG, "startVoiceRecognition: Call in progress");
- return false;
- }
-
- mVoiceRecognitionStarted = true;
-
- if (mWaitingForVoiceRecognition) {
- // HF initiated
- mWaitingForVoiceRecognition = false;
- sendURC("OK");
- } else {
- // AG initiated
- sendURC("+BVRA: 1");
- }
- boolean ret = audioOn();
- if (ret == false) {
- mVoiceRecognitionStarted = false;
- }
- if (mStartVoiceRecognitionWakeLock.isHeld()) {
- mStartVoiceRecognitionWakeLock.release();
- }
- return ret;
- }
-
- /* package */ synchronized boolean stopVoiceRecognition() {
-
- if (!isVoiceRecognitionInProgress()) {
- return false;
- }
-
- mVoiceRecognitionStarted = false;
-
- sendURC("+BVRA: 0");
- audioOff();
- return true;
- }
-
- // Voice Recognition in Progress
- private boolean isVoiceRecognitionInProgress() {
- return (mVoiceRecognitionStarted || mWaitingForVoiceRecognition);
- }
-
- /*
- * This class broadcasts vendor-specific commands + arguments to interested receivers.
- */
- private class VendorSpecificCommandHandler extends AtCommandHandler {
-
- private String mCommandName;
-
- private int mCompanyId;
-
- private VendorSpecificCommandHandler(String commandName, int companyId) {
- mCommandName = commandName;
- mCompanyId = companyId;
- }
-
- @Override
- public AtCommandResult handleReadCommand() {
- return new AtCommandResult(AtCommandResult.ERROR);
- }
-
- @Override
- public AtCommandResult handleTestCommand() {
- return new AtCommandResult(AtCommandResult.ERROR);
- }
-
- @Override
- public AtCommandResult handleActionCommand() {
- return new AtCommandResult(AtCommandResult.ERROR);
- }
-
- @Override
- public AtCommandResult handleSetCommand(Object[] arguments) {
- broadcastVendorSpecificEventIntent(mCommandName,
- mCompanyId,
- BluetoothHeadset.AT_CMD_TYPE_SET,
- arguments,
- mHeadset.getRemoteDevice());
- return new AtCommandResult(AtCommandResult.OK);
- }
- }
-
- private boolean inDebug() {
- return DBG && SystemProperties.getBoolean(DebugThread.DEBUG_HANDSFREE, false);
- }
-
- private boolean allowAudioAnytime() {
- return inDebug() && SystemProperties.getBoolean(DebugThread.DEBUG_HANDSFREE_AUDIO_ANYTIME,
- false);
- }
-
- private void startDebug() {
- if (DBG && mDebugThread == null) {
- mDebugThread = new DebugThread();
- mDebugThread.start();
- }
- }
-
- private void stopDebug() {
- if (mDebugThread != null) {
- mDebugThread.interrupt();
- mDebugThread = null;
- }
- }
-
- // VirtualCall SCO support
- //
-
- // Cellular call in progress
- private boolean isCellularCallInProgress() {
- if (mCM.hasActiveFgCall() || mCM.hasActiveRingingCall()) return true;
- return false;
- }
-
- // Virtual Call in Progress
- private boolean isVirtualCallInProgress() {
- return mVirtualCallStarted;
- }
-
- void setVirtualCallInProgress(boolean state) {
- mVirtualCallStarted = state;
- }
-
- //NOTE: Currently the VirtualCall API does not allow the application to initiate a call
- // transfer. Call transfer may be initiated from the handsfree device and this is handled by
- // the VirtualCall API
- synchronized boolean initiateScoUsingVirtualVoiceCall() {
- if (DBG) log("initiateScoUsingVirtualVoiceCall: Received");
- // 1. Check if the SCO state is idle
- if (isCellularCallInProgress() || isVoiceRecognitionInProgress()) {
- Log.e(TAG, "initiateScoUsingVirtualVoiceCall: Call in progress");
- return false;
- }
-
- // 2. Perform outgoing call setup procedure
- if (mBluetoothPhoneState.sendUpdate() && !isVirtualCallInProgress()) {
- AtCommandResult result = new AtCommandResult(AtCommandResult.UNSOLICITED);
- // outgoing call
- result.addResponse("+CIEV: 3,2");
- result.addResponse("+CIEV: 2,1");
- result.addResponse("+CIEV: 3,0");
- sendURC(result.toString());
- if (DBG) Log.d(TAG, "initiateScoUsingVirtualVoiceCall: Sent Call-setup procedure");
- }
-
- mVirtualCallStarted = true;
-
- // 3. Open the Audio Connection
- if (audioOn() == false) {
- log("initiateScoUsingVirtualVoiceCall: audioON failed");
- terminateScoUsingVirtualVoiceCall();
- return false;
- }
-
- mAudioPossible = true;
-
- // Done
- if (DBG) log("initiateScoUsingVirtualVoiceCall: Done");
- return true;
- }
-
- synchronized boolean terminateScoUsingVirtualVoiceCall() {
- if (DBG) log("terminateScoUsingVirtualVoiceCall: Received");
-
- if (!isVirtualCallInProgress()) {
- return false;
- }
-
- // 1. Release audio connection
- audioOff();
-
- // 2. terminate call-setup
- if (mBluetoothPhoneState.sendUpdate()) {
- AtCommandResult result = new AtCommandResult(AtCommandResult.UNSOLICITED);
- // outgoing call
- result.addResponse("+CIEV: 2,0");
- sendURC(result.toString());
- if (DBG) log("terminateScoUsingVirtualVoiceCall: Sent Call-setup procedure");
- }
- mVirtualCallStarted = false;
- mAudioPossible = false;
-
- // Done
- if (DBG) log("terminateScoUsingVirtualVoiceCall: Done");
- return true;
- }
-
-
- /** Debug thread to read debug properties - runs when debug.bt.hfp is true
- * at the time a bluetooth handsfree device is connected. Debug properties
- * are polled and mock updates sent every 1 second */
- private class DebugThread extends Thread {
- /** Turns on/off handsfree profile debugging mode */
- static final String DEBUG_HANDSFREE = "debug.bt.hfp";
-
- /** Mock battery level change - use 0 to 5 */
- static final String DEBUG_HANDSFREE_BATTERY = "debug.bt.hfp.battery";
-
- /** Mock no cellular service when false */
- static final String DEBUG_HANDSFREE_SERVICE = "debug.bt.hfp.service";
-
- /** Mock cellular roaming when true */
- static final String DEBUG_HANDSFREE_ROAM = "debug.bt.hfp.roam";
-
- /** false to true transition will force an audio (SCO) connection to
- * be established. true to false will force audio to be disconnected
- */
- static final String DEBUG_HANDSFREE_AUDIO = "debug.bt.hfp.audio";
-
- /** true allows incoming SCO connection out of call.
- */
- static final String DEBUG_HANDSFREE_AUDIO_ANYTIME = "debug.bt.hfp.audio_anytime";
-
- /** Mock signal strength change in ASU - use 0 to 31 */
- static final String DEBUG_HANDSFREE_SIGNAL = "debug.bt.hfp.signal";
-
- /** Debug AT+CLCC: print +CLCC result */
- static final String DEBUG_HANDSFREE_CLCC = "debug.bt.hfp.clcc";
-
- /** Debug AT+BSIR - Send In Band Ringtones Unsolicited AT command.
- * debug.bt.unsol.inband = 0 => AT+BSIR = 0 sent by the AG
- * debug.bt.unsol.inband = 1 => AT+BSIR = 0 sent by the AG
- * Other values are ignored.
- */
-
- static final String DEBUG_UNSOL_INBAND_RINGTONE = "debug.bt.unsol.inband";
-
- @Override
- public void run() {
- boolean oldService = true;
- boolean oldRoam = false;
- boolean oldAudio = false;
-
- while (!isInterrupted() && inDebug()) {
- int batteryLevel = SystemProperties.getInt(DEBUG_HANDSFREE_BATTERY, -1);
- if (batteryLevel >= 0 && batteryLevel <= 5) {
- Intent intent = new Intent();
- intent.putExtra("level", batteryLevel);
- intent.putExtra("scale", 5);
- mBluetoothPhoneState.updateBatteryState(intent);
- }
-
- boolean serviceStateChanged = false;
- if (SystemProperties.getBoolean(DEBUG_HANDSFREE_SERVICE, true) != oldService) {
- oldService = !oldService;
- serviceStateChanged = true;
- }
- if (SystemProperties.getBoolean(DEBUG_HANDSFREE_ROAM, false) != oldRoam) {
- oldRoam = !oldRoam;
- serviceStateChanged = true;
- }
- if (serviceStateChanged) {
- Bundle b = new Bundle();
- b.putInt("state", oldService ? 0 : 1);
- b.putBoolean("roaming", oldRoam);
- mBluetoothPhoneState.updateServiceState(true, ServiceState.newFromBundle(b));
- }
-
- if (SystemProperties.getBoolean(DEBUG_HANDSFREE_AUDIO, false) != oldAudio) {
- oldAudio = !oldAudio;
- if (oldAudio) {
- audioOn();
- } else {
- audioOff();
- }
- }
-
- int signalLevel = SystemProperties.getInt(DEBUG_HANDSFREE_SIGNAL, -1);
- if (signalLevel >= 0 && signalLevel <= 31) {
- SignalStrength signalStrength = new SignalStrength(signalLevel, -1, -1, -1,
- -1, -1, -1, true);
- Intent intent = new Intent();
- Bundle data = new Bundle();
- signalStrength.fillInNotifierBundle(data);
- intent.putExtras(data);
- mBluetoothPhoneState.updateSignalState(intent);
- }
-
- if (SystemProperties.getBoolean(DEBUG_HANDSFREE_CLCC, false)) {
- log(gsmGetClccResult().toString());
- }
- try {
- sleep(1000); // 1 second
- } catch (InterruptedException e) {
- break;
- }
-
- int inBandRing =
- SystemProperties.getInt(DEBUG_UNSOL_INBAND_RINGTONE, -1);
- if (inBandRing == 0 || inBandRing == 1) {
- AtCommandResult result =
- new AtCommandResult(AtCommandResult.UNSOLICITED);
- result.addResponse("+BSIR: " + inBandRing);
- sendURC(result.toString());
- }
- }
- }
- }
-
- public void cdmaSwapSecondCallState() {
- if (VDBG) log("cdmaSetSecondCallState: Toggling mCdmaIsSecondCallActive");
- mCdmaIsSecondCallActive = !mCdmaIsSecondCallActive;
- mCdmaCallsSwapped = true;
- }
-
- public void cdmaSetSecondCallState(boolean state) {
- if (VDBG) log("cdmaSetSecondCallState: Setting mCdmaIsSecondCallActive to " + state);
- mCdmaIsSecondCallActive = state;
-
- if (!mCdmaIsSecondCallActive) {
- mCdmaCallsSwapped = false;
- }
- }
-
- private static void log(String msg) {
- Log.d(TAG, msg);
- }
-}
diff --git a/src/com/android/phone/BluetoothHeadsetService.java b/src/com/android/phone/BluetoothHeadsetService.java
deleted file mode 100755
index db64376..0000000
--- a/src/com/android/phone/BluetoothHeadsetService.java
+++ /dev/null
@@ -1,927 +0,0 @@
-/*
- * 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.Service;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothAudioGateway;
-import android.bluetooth.BluetoothAudioGateway.IncomingConnectionInfo;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHeadset;
-import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothUuid;
-import android.bluetooth.HeadsetBase;
-import android.bluetooth.IBluetooth;
-import android.bluetooth.IBluetoothHeadset;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.media.AudioManager;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.ParcelUuid;
-import android.os.PowerManager;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.provider.Settings;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * Provides Bluetooth Headset and Handsfree profile, as a service in
- * the Phone application.
- * @hide
- */
-public class BluetoothHeadsetService extends Service {
- private static final String TAG = "Bluetooth HSHFP";
- private static final boolean DBG = true;
-
- private static final String PREF_NAME = BluetoothHeadsetService.class.getSimpleName();
- private static final String PREF_LAST_HEADSET = "lastHeadsetAddress";
-
- private static final int PHONE_STATE_CHANGED = 1;
-
- private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
- private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
-
- private static boolean sHasStarted = false;
-
- private BluetoothDevice mDeviceSdpQuery;
- private BluetoothAdapter mAdapter;
- private IBluetooth mBluetoothService;
- private PowerManager mPowerManager;
- private BluetoothAudioGateway mAg;
- private BluetoothHandsfree mBtHandsfree;
- private ConcurrentHashMap<BluetoothDevice, BluetoothRemoteHeadset> mRemoteHeadsets;
- private BluetoothDevice mAudioConnectedDevice;
-
- @Override
- public void onCreate() {
- super.onCreate();
- mAdapter = BluetoothAdapter.getDefaultAdapter();
- mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
- mBtHandsfree = PhoneApp.getInstance().getBluetoothHandsfree();
- mAg = new BluetoothAudioGateway(mAdapter);
- IntentFilter filter = new IntentFilter(
- BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED);
- filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
- filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
- filter.addAction(BluetoothDevice.ACTION_UUID);
- registerReceiver(mBluetoothReceiver, filter);
-
- IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE);
- if (b == null) {
- throw new RuntimeException("Bluetooth service not available");
- }
- mBluetoothService = IBluetooth.Stub.asInterface(b);
- mRemoteHeadsets = new ConcurrentHashMap<BluetoothDevice, BluetoothRemoteHeadset>();
- }
-
- private class BluetoothRemoteHeadset {
- private int mState;
- private int mAudioState;
- private int mHeadsetType;
- private HeadsetBase mHeadset;
- private IncomingConnectionInfo mIncomingInfo;
-
- BluetoothRemoteHeadset() {
- mState = BluetoothProfile.STATE_DISCONNECTED;
- mHeadsetType = BluetoothHandsfree.TYPE_UNKNOWN;
- mHeadset = null;
- mIncomingInfo = null;
- mAudioState = BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
- }
-
- BluetoothRemoteHeadset(int headsetType, IncomingConnectionInfo incomingInfo) {
- mState = BluetoothProfile.STATE_DISCONNECTED;
- mHeadsetType = headsetType;
- mHeadset = null;
- mIncomingInfo = incomingInfo;
- mAudioState = BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
- }
- }
-
- synchronized private BluetoothDevice getCurrentDevice() {
- for (BluetoothDevice device : mRemoteHeadsets.keySet()) {
- int state = mRemoteHeadsets.get(device).mState;
- if (state == BluetoothProfile.STATE_CONNECTING ||
- state == BluetoothProfile.STATE_CONNECTED) {
- return device;
- }
- }
- return null;
- }
-
- @Override
- public void onStart(Intent intent, int startId) {
- if (mAdapter == null) {
- Log.w(TAG, "Stopping BluetoothHeadsetService: device does not have BT");
- stopSelf();
- } else {
- if (!sHasStarted) {
- if (DBG) log("Starting BluetoothHeadsetService");
- if (mAdapter.isEnabled()) {
- mAg.start(mIncomingConnectionHandler);
- mBtHandsfree.onBluetoothEnabled();
- }
- sHasStarted = true;
- }
- }
- }
-
- private final Handler mIncomingConnectionHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- synchronized(BluetoothHeadsetService.this) {
- IncomingConnectionInfo info = (IncomingConnectionInfo)msg.obj;
- int type = BluetoothHandsfree.TYPE_UNKNOWN;
- switch(msg.what) {
- case BluetoothAudioGateway.MSG_INCOMING_HEADSET_CONNECTION:
- type = BluetoothHandsfree.TYPE_HEADSET;
- break;
- case BluetoothAudioGateway.MSG_INCOMING_HANDSFREE_CONNECTION:
- type = BluetoothHandsfree.TYPE_HANDSFREE;
- break;
- }
-
- Log.i(TAG, "Incoming rfcomm (" + BluetoothHandsfree.typeToString(type) +
- ") connection from " + info.mRemoteDevice + "on channel " +
- info.mRfcommChan);
-
- int priority = BluetoothProfile.PRIORITY_OFF;
- HeadsetBase headset;
- priority = getPriority(info.mRemoteDevice);
- if (priority <= BluetoothProfile.PRIORITY_OFF) {
- Log.i(TAG, "Rejecting incoming connection because priority = " + priority);
-
- headset = new HeadsetBase(mPowerManager, mAdapter,
- info.mRemoteDevice,
- info.mSocketFd, info.mRfcommChan,
- null);
- headset.disconnect();
- try {
- mBluetoothService.notifyIncomingConnection(info.mRemoteDevice.getAddress(),
- true);
- } catch (RemoteException e) {
- Log.e(TAG, "notifyIncomingConnection", e);
- }
- return;
- }
-
- BluetoothRemoteHeadset remoteHeadset;
- BluetoothDevice device = getCurrentDevice();
-
- int state = BluetoothProfile.STATE_DISCONNECTED;
- if (device != null) {
- state = mRemoteHeadsets.get(device).mState;
- }
-
- switch (state) {
- case BluetoothProfile.STATE_DISCONNECTED:
- // headset connecting us, lets join
- remoteHeadset = new BluetoothRemoteHeadset(type, info);
- mRemoteHeadsets.put(info.mRemoteDevice, remoteHeadset);
-
- try {
- mBluetoothService.notifyIncomingConnection(
- info.mRemoteDevice.getAddress(), false);
- } catch (RemoteException e) {
- Log.e(TAG, "notifyIncomingConnection");
- }
- break;
- case BluetoothProfile.STATE_CONNECTING:
- if (!info.mRemoteDevice.equals(device)) {
- // different headset, ignoring
- Log.i(TAG, "Already attempting connect to " + device +
- ", disconnecting " + info.mRemoteDevice);
-
- headset = new HeadsetBase(mPowerManager, mAdapter,
- info.mRemoteDevice,
- info.mSocketFd, info.mRfcommChan,
- null);
- headset.disconnect();
- break;
- }
-
- // Incoming and Outgoing connections to the same headset.
- // The state machine manager will cancel outgoing and accept the incoming one.
- // Update the state
- mRemoteHeadsets.get(info.mRemoteDevice).mHeadsetType = type;
- mRemoteHeadsets.get(info.mRemoteDevice).mIncomingInfo = info;
-
- try {
- mBluetoothService.notifyIncomingConnection(
- info.mRemoteDevice.getAddress(), false);
- } catch (RemoteException e) {
- Log.e(TAG, "notifyIncomingConnection");
- }
- break;
- case BluetoothProfile.STATE_CONNECTED:
- Log.i(TAG, "Already connected to " + device + ", disconnecting " +
- info.mRemoteDevice);
- rejectIncomingConnection(info);
- break;
- }
- }
- }
- };
-
- private void rejectIncomingConnection(IncomingConnectionInfo info) {
- HeadsetBase headset = new HeadsetBase(mPowerManager, mAdapter,
- info.mRemoteDevice, info.mSocketFd, info.mRfcommChan, null);
- headset.disconnect();
- }
-
-
- private final BroadcastReceiver mBluetoothReceiver = new BroadcastReceiver() {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- BluetoothDevice device =
- intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-
- BluetoothDevice currDevice = getCurrentDevice();
- int state = BluetoothProfile.STATE_DISCONNECTED;
- if (currDevice != null) {
- state = mRemoteHeadsets.get(currDevice).mState;
- }
-
- if ((state == BluetoothProfile.STATE_CONNECTED ||
- state == BluetoothProfile.STATE_CONNECTING) &&
- action.equals(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED) &&
- device.equals(currDevice)) {
- try {
- mBinder.disconnect(currDevice);
- } catch (RemoteException e) {}
- } else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
- switch (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
- BluetoothAdapter.ERROR)) {
- case BluetoothAdapter.STATE_ON:
- mAg.start(mIncomingConnectionHandler);
- mBtHandsfree.onBluetoothEnabled();
- break;
- case BluetoothAdapter.STATE_TURNING_OFF:
- mBtHandsfree.onBluetoothDisabled();
- mAg.stop();
- if (currDevice != null) {
- try {
- mBinder.disconnect(currDevice);
- } catch (RemoteException e) {}
- }
- break;
- }
- } else if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
- int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
- if (streamType == AudioManager.STREAM_BLUETOOTH_SCO) {
- mBtHandsfree.sendScoGainUpdate(intent.getIntExtra(
- AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0));
- }
-
- } else if (action.equals(BluetoothDevice.ACTION_UUID)) {
- if (device.equals(mDeviceSdpQuery) && device.equals(currDevice)) {
- // We have got SDP records for the device we are interested in.
- getSdpRecordsAndConnect(device);
- }
- }
- }
- };
-
- private static final int CONNECT_HEADSET_DELAYED = 1;
- private Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case CONNECT_HEADSET_DELAYED:
- BluetoothDevice device = (BluetoothDevice) msg.obj;
- getSdpRecordsAndConnect(device);
- break;
- }
- }
- };
-
- @Override
- public IBinder onBind(Intent intent) {
- return mBinder;
- }
-
- // ------------------------------------------------------------------
- // Bluetooth Headset Connect
- // ------------------------------------------------------------------
- private static final int RFCOMM_CONNECTED = 1;
- private static final int RFCOMM_ERROR = 2;
-
- private long mTimestamp;
-
- /**
- * Thread for RFCOMM connection
- * Messages are sent to mConnectingStatusHandler as connection progresses.
- */
- private RfcommConnectThread mConnectThread;
- private class RfcommConnectThread extends Thread {
- private BluetoothDevice device;
- private int channel;
- private int type;
-
- private static final int EINTERRUPT = -1000;
- private static final int ECONNREFUSED = -111;
-
- public RfcommConnectThread(BluetoothDevice device, int channel, int type) {
- super();
- this.device = device;
- this.channel = channel;
- this.type = type;
- }
-
- private int waitForConnect(HeadsetBase headset) {
- // Try to connect for 20 seconds
- int result = 0;
- for (int i=0; i < 40 && result == 0; i++) {
- // waitForAsyncConnect returns 0 on timeout, 1 on success, < 0 on error.
- result = headset.waitForAsyncConnect(500, mConnectedStatusHandler);
- if (isInterrupted()) {
- headset.disconnect();
- return EINTERRUPT;
- }
- }
- return result;
- }
-
- @Override
- public void run() {
- long timestamp;
-
- timestamp = System.currentTimeMillis();
- HeadsetBase headset = new HeadsetBase(mPowerManager, mAdapter,
- device, channel);
-
- int result = waitForConnect(headset);
-
- if (result != EINTERRUPT && result != 1) {
- if (result == ECONNREFUSED && mDeviceSdpQuery == null) {
- // The rfcomm channel number might have changed, do SDP
- // query and try to connect again.
- mDeviceSdpQuery = getCurrentDevice();
- device.fetchUuidsWithSdp();
- mConnectThread = null;
- return;
- } else {
- Log.i(TAG, "Trying to connect to rfcomm socket again after 1 sec");
- try {
- sleep(1000); // 1 second
- } catch (InterruptedException e) {
- return;
- }
- }
- result = waitForConnect(headset);
- }
- mDeviceSdpQuery = null;
- if (result == EINTERRUPT) return;
-
- if (DBG) log("RFCOMM connection attempt took " +
- (System.currentTimeMillis() - timestamp) + " ms");
- if (isInterrupted()) {
- headset.disconnect();
- return;
- }
- if (result < 0) {
- Log.w(TAG, "headset.waitForAsyncConnect() error: " + result);
- mConnectingStatusHandler.obtainMessage(RFCOMM_ERROR).sendToTarget();
- return;
- } else if (result == 0) {
- mConnectingStatusHandler.obtainMessage(RFCOMM_ERROR).sendToTarget();
- Log.w(TAG, "mHeadset.waitForAsyncConnect() error: " + result + "(timeout)");
- return;
- } else {
- mConnectingStatusHandler.obtainMessage(RFCOMM_CONNECTED, headset).sendToTarget();
- }
- }
- }
-
- /**
- * Receives events from mConnectThread back in the main thread.
- */
- private final Handler mConnectingStatusHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- BluetoothDevice device = getCurrentDevice();
- if (device == null ||
- mRemoteHeadsets.get(device).mState != BluetoothProfile.STATE_CONNECTING) {
- return; // stale events
- }
-
- switch (msg.what) {
- case RFCOMM_ERROR:
- if (DBG) log("Rfcomm error");
- mConnectThread = null;
- setState(device, BluetoothProfile.STATE_DISCONNECTED);
- break;
- case RFCOMM_CONNECTED:
- if (DBG) log("Rfcomm connected");
- mConnectThread = null;
- HeadsetBase headset = (HeadsetBase)msg.obj;
- setState(device, BluetoothProfile.STATE_CONNECTED);
-
- mRemoteHeadsets.get(device).mHeadset = headset;
- mBtHandsfree.connectHeadset(headset, mRemoteHeadsets.get(device).mHeadsetType);
- break;
- }
- }
- };
-
- /**
- * Receives events from a connected RFCOMM socket back in the main thread.
- */
- private final Handler mConnectedStatusHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case HeadsetBase.RFCOMM_DISCONNECTED:
- mBtHandsfree.resetAtState();
- mBtHandsfree.setVirtualCallInProgress(false);
- BluetoothDevice device = getCurrentDevice();
- if (device != null) {
- setState(device, BluetoothProfile.STATE_DISCONNECTED);
- }
- break;
- }
- }
- };
-
- private synchronized void setState(BluetoothDevice device, int state) {
- int prevState = mRemoteHeadsets.get(device).mState;
- if (state != prevState) {
- if (DBG) log("Device: " + device +
- " Headset state" + prevState + " -> " + state);
- if (prevState == BluetoothProfile.STATE_CONNECTED) {
- // Headset is disconnecting, stop the parser.
- mBtHandsfree.disconnectHeadset();
- }
- Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
- intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
- intent.putExtra(BluetoothProfile.EXTRA_STATE, state);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
- if (state == BluetoothProfile.STATE_DISCONNECTED) {
- mRemoteHeadsets.get(device).mHeadset = null;
- mRemoteHeadsets.get(device).mHeadsetType = BluetoothHandsfree.TYPE_UNKNOWN;
- }
-
- mRemoteHeadsets.get(device).mState = state;
-
- sendBroadcast(intent, BLUETOOTH_PERM);
- if (state == BluetoothHeadset.STATE_CONNECTED) {
- // Set the priority to AUTO_CONNECT
- setPriority(device, BluetoothHeadset.PRIORITY_AUTO_CONNECT);
- adjustOtherHeadsetPriorities(device);
- }
- try {
- mBluetoothService.sendConnectionStateChange(device, BluetoothProfile.HEADSET,
- state, prevState);
- } catch (RemoteException e) {
- Log.e(TAG, "sendConnectionStateChange: exception");
- }
- }
- }
-
- private void adjustOtherHeadsetPriorities(BluetoothDevice connectedDevice) {
- for (BluetoothDevice device : mAdapter.getBondedDevices()) {
- if (getPriority(device) >= BluetoothHeadset.PRIORITY_AUTO_CONNECT &&
- !device.equals(connectedDevice)) {
- setPriority(device, BluetoothHeadset.PRIORITY_ON);
- }
- }
- }
-
- private void setPriority(BluetoothDevice device, int priority) {
- try {
- mBinder.setPriority(device, priority);
- } catch (RemoteException e) {
- Log.e(TAG, "Error while setting priority for: " + device);
- }
- }
-
- private int getPriority(BluetoothDevice device) {
- try {
- return mBinder.getPriority(device);
- } catch (RemoteException e) {
- Log.e(TAG, "Error while getting priority for: " + device);
- }
- return BluetoothProfile.PRIORITY_UNDEFINED;
- }
-
- private synchronized void getSdpRecordsAndConnect(BluetoothDevice device) {
- if (!device.equals(getCurrentDevice())) {
- // stale
- return;
- }
-
- // Check if incoming connection has already connected.
- if (mRemoteHeadsets.get(device).mState == BluetoothProfile.STATE_CONNECTED) {
- return;
- }
-
- ParcelUuid[] uuids = device.getUuids();
- ParcelUuid[] localUuids = mAdapter.getUuids();
- int type = BluetoothHandsfree.TYPE_UNKNOWN;
- if (uuids != null) {
- if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree) &&
- BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.Handsfree_AG)) {
- log("SDP UUID: TYPE_HANDSFREE");
- type = BluetoothHandsfree.TYPE_HANDSFREE;
- mRemoteHeadsets.get(device).mHeadsetType = type;
- int channel = device.getServiceChannel(BluetoothUuid.Handsfree);
- mConnectThread = new RfcommConnectThread(device, channel, type);
- if (mAdapter.isDiscovering()) {
- mAdapter.cancelDiscovery();
- }
- mConnectThread.start();
- if (getPriority(device) < BluetoothHeadset.PRIORITY_AUTO_CONNECT) {
- setPriority(device, BluetoothHeadset.PRIORITY_AUTO_CONNECT);
- }
- return;
- } else if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP) &&
- BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.HSP_AG)) {
- log("SDP UUID: TYPE_HEADSET");
- type = BluetoothHandsfree.TYPE_HEADSET;
- mRemoteHeadsets.get(device).mHeadsetType = type;
- int channel = device.getServiceChannel(BluetoothUuid.HSP);
- mConnectThread = new RfcommConnectThread(device, channel, type);
- if (mAdapter.isDiscovering()) {
- mAdapter.cancelDiscovery();
- }
- mConnectThread.start();
- if (getPriority(device) < BluetoothHeadset.PRIORITY_AUTO_CONNECT) {
- setPriority(device, BluetoothHeadset.PRIORITY_AUTO_CONNECT);
- }
- return;
- }
- }
- log("SDP UUID: TYPE_UNKNOWN");
- mRemoteHeadsets.get(device).mHeadsetType = type;
- setState(device, BluetoothProfile.STATE_DISCONNECTED);
- return;
- }
-
- /**
- * Handlers for incoming service calls
- */
- private final IBluetoothHeadset.Stub mBinder = new IBluetoothHeadset.Stub() {
- public int getConnectionState(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- BluetoothRemoteHeadset headset = mRemoteHeadsets.get(device);
- if (headset == null) {
- return BluetoothProfile.STATE_DISCONNECTED;
- }
- return headset.mState;
- }
-
- public List<BluetoothDevice> getConnectedDevices() {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return getDevicesMatchingConnectionStates(
- new int[] {BluetoothProfile.STATE_CONNECTED});
- }
-
- public boolean connect(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH_ADMIN permission");
- synchronized (BluetoothHeadsetService.this) {
- BluetoothDevice currDevice = getCurrentDevice();
-
- if (currDevice == device ||
- getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
- return false;
- }
- if (currDevice != null) {
- disconnect(currDevice);
- }
- try {
- return mBluetoothService.connectHeadset(device.getAddress());
- } catch (RemoteException e) {
- Log.e(TAG, "connectHeadset");
- return false;
- }
- }
- }
-
- public boolean disconnect(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH_ADMIN permission");
- synchronized (BluetoothHeadsetService.this) {
- BluetoothRemoteHeadset headset = mRemoteHeadsets.get(device);
- if (headset == null ||
- headset.mState == BluetoothProfile.STATE_DISCONNECTED ||
- headset.mState == BluetoothProfile.STATE_DISCONNECTING) {
- return false;
- }
- try {
- return mBluetoothService.disconnectHeadset(device.getAddress());
- } catch (RemoteException e) {
- Log.e(TAG, "disconnectHeadset");
- return false;
- }
- }
- }
-
- public synchronized boolean isAudioConnected(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- if (device.equals(mAudioConnectedDevice)) return true;
- return false;
- }
-
- public synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- List<BluetoothDevice> headsets = new ArrayList<BluetoothDevice>();
- for (BluetoothDevice device: mRemoteHeadsets.keySet()) {
- int headsetState = getConnectionState(device);
- for (int state : states) {
- if (state == headsetState) {
- headsets.add(device);
- break;
- }
- }
- }
- return headsets;
- }
-
- public boolean startVoiceRecognition(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- synchronized (BluetoothHeadsetService.this) {
- if (device == null ||
- mRemoteHeadsets.get(device) == null ||
- mRemoteHeadsets.get(device).mState != BluetoothProfile.STATE_CONNECTED) {
- return false;
- }
- return mBtHandsfree.startVoiceRecognition();
- }
- }
-
- public boolean stopVoiceRecognition(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- synchronized (BluetoothHeadsetService.this) {
- if (device == null ||
- mRemoteHeadsets.get(device) == null ||
- mRemoteHeadsets.get(device).mState != BluetoothProfile.STATE_CONNECTED) {
- return false;
- }
-
- return mBtHandsfree.stopVoiceRecognition();
- }
- }
-
- public int getBatteryUsageHint(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
- return HeadsetBase.getAtInputCount();
- }
-
- public int getPriority(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH_ADMIN permission");
- synchronized (BluetoothHeadsetService.this) {
- int priority = Settings.Secure.getInt(getContentResolver(),
- Settings.Secure.getBluetoothHeadsetPriorityKey(device.getAddress()),
- BluetoothProfile.PRIORITY_UNDEFINED);
- return priority;
- }
- }
-
- public boolean setPriority(BluetoothDevice device, int priority) {
- enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH_ADMIN permission");
- synchronized (BluetoothHeadsetService.this) {
- Settings.Secure.putInt(getContentResolver(),
- Settings.Secure.getBluetoothHeadsetPriorityKey(device.getAddress()),
- priority);
- if (DBG) log("Saved priority " + device + " = " + priority);
- return true;
- }
- }
-
- public boolean createIncomingConnect(BluetoothDevice device) {
- synchronized (BluetoothHeadsetService.this) {
- HeadsetBase headset;
- setState(device, BluetoothProfile.STATE_CONNECTING);
-
- IncomingConnectionInfo info = mRemoteHeadsets.get(device).mIncomingInfo;
- headset = new HeadsetBase(mPowerManager, mAdapter,
- device,
- info.mSocketFd, info.mRfcommChan,
- mConnectedStatusHandler);
-
- mRemoteHeadsets.get(device).mHeadset = headset;
-
- mConnectingStatusHandler.obtainMessage(RFCOMM_CONNECTED, headset).sendToTarget();
- return true;
- }
- }
-
- public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- synchronized (BluetoothHeadsetService.this) {
- if (device == null ||
- mRemoteHeadsets.get(device) == null ||
- mRemoteHeadsets.get(device).mState != BluetoothProfile.STATE_CONNECTED ||
- getAudioState(device) != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
- return false;
- }
- return mBtHandsfree.initiateScoUsingVirtualVoiceCall();
- }
- }
-
- public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- synchronized (BluetoothHeadsetService.this) {
- if (device == null ||
- mRemoteHeadsets.get(device) == null ||
- mRemoteHeadsets.get(device).mState != BluetoothProfile.STATE_CONNECTED ||
- getAudioState(device) == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
- return false;
- }
- return mBtHandsfree.terminateScoUsingVirtualVoiceCall();
- }
- }
-
- public boolean rejectIncomingConnect(BluetoothDevice device) {
- synchronized (BluetoothHeadsetService.this) {
- BluetoothRemoteHeadset headset = mRemoteHeadsets.get(device);
- if (headset != null) {
- IncomingConnectionInfo info = headset.mIncomingInfo;
- rejectIncomingConnection(info);
- } else {
- Log.e(TAG, "Error no record of remote headset");
- }
- return true;
- }
- }
-
- public boolean acceptIncomingConnect(BluetoothDevice device) {
- synchronized (BluetoothHeadsetService.this) {
- HeadsetBase headset;
- BluetoothRemoteHeadset cachedHeadset = mRemoteHeadsets.get(device);
- if (cachedHeadset == null) {
- Log.e(TAG, "Cached Headset is Null in acceptIncomingConnect");
- return false;
- }
- IncomingConnectionInfo info = cachedHeadset.mIncomingInfo;
- headset = new HeadsetBase(mPowerManager, mAdapter,
- device,
- info.mSocketFd, info.mRfcommChan,
- mConnectedStatusHandler);
-
- setState(device, BluetoothProfile.STATE_CONNECTED);
-
- cachedHeadset.mHeadset = headset;
- mBtHandsfree.connectHeadset(headset, cachedHeadset.mHeadsetType);
-
- if (DBG) log("Successfully used incoming connection");
- return true;
- }
- }
-
- public boolean cancelConnectThread() {
- synchronized (BluetoothHeadsetService.this) {
- if (mConnectThread != null) {
- // cancel the connection thread
- mConnectThread.interrupt();
- try {
- mConnectThread.join();
- } catch (InterruptedException e) {
- Log.e(TAG, "Connection cancelled twice?", e);
- }
- mConnectThread = null;
- }
- return true;
- }
- }
-
- public boolean connectHeadsetInternal(BluetoothDevice device) {
- synchronized (BluetoothHeadsetService.this) {
- BluetoothDevice currDevice = getCurrentDevice();
- if (currDevice == null) {
- BluetoothRemoteHeadset headset = new BluetoothRemoteHeadset();
- mRemoteHeadsets.put(device, headset);
-
- setState(device, BluetoothProfile.STATE_CONNECTING);
- if (device.getUuids() == null) {
- // We might not have got the UUID change notification from
- // Bluez yet, if we have just paired. Try after 1.5 secs.
- Message msg = new Message();
- msg.what = CONNECT_HEADSET_DELAYED;
- msg.obj = device;
- mHandler.sendMessageDelayed(msg, 1500);
- } else {
- getSdpRecordsAndConnect(device);
- }
- return true;
- } else {
- Log.w(TAG, "connectHeadset(" + device + "): failed: already in state " +
- mRemoteHeadsets.get(currDevice).mState +
- " with headset " + currDevice);
- }
- return false;
- }
- }
-
- public boolean disconnectHeadsetInternal(BluetoothDevice device) {
- synchronized (BluetoothHeadsetService.this) {
- BluetoothRemoteHeadset remoteHeadset = mRemoteHeadsets.get(device);
- if (remoteHeadset == null) return false;
-
- if (remoteHeadset.mState == BluetoothProfile.STATE_CONNECTED) {
- // Send a dummy battery level message to force headset
- // out of sniff mode so that it will immediately notice
- // the disconnection. We are currently sending it for
- // handsfree only.
- // TODO: Call hci_conn_enter_active_mode() from
- // rfcomm_send_disc() in the kernel instead.
- // See http://b/1716887
- setState(device, BluetoothProfile.STATE_DISCONNECTING);
-
- HeadsetBase headset = remoteHeadset.mHeadset;
- if (remoteHeadset.mHeadsetType == BluetoothHandsfree.TYPE_HANDSFREE) {
- headset.sendURC("+CIEV: 7,3");
- }
-
- if (headset != null) {
- headset.disconnect();
- headset = null;
- }
- setState(device, BluetoothProfile.STATE_DISCONNECTED);
- return true;
- } else if (remoteHeadset.mState == BluetoothProfile.STATE_CONNECTING) {
- // The state machine would have canceled the connect thread.
- // Just set the state here.
- setState(device, BluetoothProfile.STATE_DISCONNECTED);
- return true;
- }
- return false;
- }
- }
-
- public boolean setAudioState(BluetoothDevice device, int state) {
- // mRemoteHeadsets handles put/get concurrency by itself
- int prevState = mRemoteHeadsets.get(device).mAudioState;
- mRemoteHeadsets.get(device).mAudioState = state;
- if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
- mAudioConnectedDevice = device;
- } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
- mAudioConnectedDevice = null;
- }
- Intent intent = new Intent(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
- intent.putExtra(BluetoothHeadset.EXTRA_STATE, state);
- intent.putExtra(BluetoothHeadset.EXTRA_PREVIOUS_STATE, prevState);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
- sendBroadcast(intent, android.Manifest.permission.BLUETOOTH);
- if (DBG) log("AudioStateIntent: " + device + " State: " + state
- + " PrevState: " + prevState);
- return true;
- }
-
- public int getAudioState(BluetoothDevice device) {
- // mRemoteHeadsets handles put/get concurrency by itself
- BluetoothRemoteHeadset headset = mRemoteHeadsets.get(device);
- if (headset == null) return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
-
- return headset.mAudioState;
- }
- };
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- if (DBG) log("Stopping BluetoothHeadsetService");
- unregisterReceiver(mBluetoothReceiver);
- mBtHandsfree.onBluetoothDisabled();
- mAg.stop();
- sHasStarted = false;
- BluetoothDevice device = getCurrentDevice();
- if (device != null) {
- setState(device, BluetoothProfile.STATE_DISCONNECTED);
- }
- }
-
-
-
- private static void log(String msg) {
- Log.d(TAG, msg);
- }
-}
diff --git a/src/com/android/phone/BluetoothPhoneService.java b/src/com/android/phone/BluetoothPhoneService.java
new file mode 100755
index 0000000..e2f48fd
--- /dev/null
+++ b/src/com/android/phone/BluetoothPhoneService.java
@@ -0,0 +1,931 @@
+/*
+ * Copyright (C) 2012 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.Service;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.IBluetoothHeadsetPhone;
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.os.SystemProperties;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.ServiceState;
+import android.util.Log;
+
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.CallManager;
+
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Bluetooth headset manager for the Phone app.
+ * @hide
+ */
+public class BluetoothPhoneService extends Service {
+ private static final String TAG = "BluetoothPhoneService";
+ private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 1)
+ && (SystemProperties.getInt("ro.debuggable", 0) == 1);
+ private static final boolean VDBG = (PhoneGlobals.DBG_LEVEL >= 2); // even more logging
+
+ private static final String MODIFY_PHONE_STATE = android.Manifest.permission.MODIFY_PHONE_STATE;
+
+ private BluetoothAdapter mAdapter;
+ private CallManager mCM;
+
+ private BluetoothHeadset mBluetoothHeadset;
+
+ private PowerManager mPowerManager;
+
+ private WakeLock mStartCallWakeLock; // held while waiting for the intent to start call
+
+ private PhoneConstants.State mPhoneState = PhoneConstants.State.IDLE;
+ CdmaPhoneCallState.PhoneCallState mCdmaThreeWayCallState =
+ CdmaPhoneCallState.PhoneCallState.IDLE;
+
+ private Call.State mForegroundCallState;
+ private Call.State mRingingCallState;
+ private CallNumber mRingNumber;
+ // number of active calls
+ int mNumActive;
+ // number of background (held) calls
+ int mNumHeld;
+
+ long mBgndEarliestConnectionTime = 0;
+
+ private boolean mRoam = false;
+
+ // CDMA specific flag used in context with BT devices having display capabilities
+ // to show which Caller is active. This state might not be always true as in CDMA
+ // networks if a caller drops off no update is provided to the Phone.
+ // This flag is just used as a toggle to provide a update to the BT device to specify
+ // which caller is active.
+ private boolean mCdmaIsSecondCallActive = false;
+ private boolean mCdmaCallsSwapped = false;
+
+ private long[] mClccTimestamps; // Timestamps associated with each clcc index
+ private boolean[] mClccUsed; // Is this clcc index in use
+
+ private static final int GSM_MAX_CONNECTIONS = 6; // Max connections allowed by GSM
+ private static final int CDMA_MAX_CONNECTIONS = 2; // Max connections allowed by CDMA
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mCM = CallManager.getInstance();
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
+ if (mAdapter == null) {
+ if (VDBG) Log.d(TAG, "mAdapter null");
+ return;
+ }
+
+ mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
+ mStartCallWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ TAG + ":StartCall");
+ mStartCallWakeLock.setReferenceCounted(false);
+
+ mAdapter.getProfileProxy(this, mProfileListener, BluetoothProfile.HEADSET);
+
+ mForegroundCallState = Call.State.IDLE;
+ mRingingCallState = Call.State.IDLE;
+ mNumActive = 0;
+ mNumHeld = 0;
+ mRingNumber = new CallNumber("", 0);;
+ mRoam = false;
+
+ updateServiceState(mCM.getDefaultPhone().getServiceState());
+ handlePreciseCallStateChange(null);
+
+ if(VDBG) Log.d(TAG, "registerForServiceStateChanged");
+ // register for updates
+ // Use the service state of default phone as BT service state to
+ // avoid situation such as no cell or wifi connection but still
+ // reporting in service (since SipPhone always reports in service).
+ mCM.getDefaultPhone().registerForServiceStateChanged(mHandler,
+ SERVICE_STATE_CHANGED, null);
+ mCM.registerForPreciseCallStateChanged(mHandler,
+ PRECISE_CALL_STATE_CHANGED, null);
+ mCM.registerForCallWaiting(mHandler,
+ PHONE_CDMA_CALL_WAITING, null);
+ // TODO(BT) registerForIncomingRing?
+ // TODO(BT) registerdisconnection?
+ mClccTimestamps = new long[GSM_MAX_CONNECTIONS];
+ mClccUsed = new boolean[GSM_MAX_CONNECTIONS];
+ for (int i = 0; i < GSM_MAX_CONNECTIONS; i++) {
+ mClccUsed[i] = false;
+ }
+ }
+
+ @Override
+ public void onStart(Intent intent, int startId) {
+ if (mAdapter == null) {
+ Log.w(TAG, "Stopping Bluetooth BluetoothPhoneService Service: device does not have BT");
+ stopSelf();
+ }
+ if (VDBG) Log.d(TAG, "BluetoothPhoneService started");
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (DBG) log("Stopping Bluetooth BluetoothPhoneService Service");
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+ private static final int SERVICE_STATE_CHANGED = 1;
+ private static final int PRECISE_CALL_STATE_CHANGED = 2;
+ private static final int PHONE_CDMA_CALL_WAITING = 3;
+ private static final int LIST_CURRENT_CALLS = 4;
+ private static final int QUERY_PHONE_STATE = 5;
+ private static final int CDMA_SWAP_SECOND_CALL_STATE = 6;
+ private static final int CDMA_SET_SECOND_CALL_STATE = 7;
+
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (VDBG) Log.d(TAG, "handleMessage: " + msg.what);
+ switch(msg.what) {
+ case SERVICE_STATE_CHANGED:
+ ServiceState state = (ServiceState) ((AsyncResult) msg.obj).result;
+ updateServiceState(state);
+ break;
+ case PRECISE_CALL_STATE_CHANGED:
+ case PHONE_CDMA_CALL_WAITING:
+ Connection connection = null;
+ if (((AsyncResult) msg.obj).result instanceof Connection) {
+ connection = (Connection) ((AsyncResult) msg.obj).result;
+ }
+ handlePreciseCallStateChange(connection);
+ break;
+ case LIST_CURRENT_CALLS:
+ handleListCurrentCalls();
+ break;
+ case QUERY_PHONE_STATE:
+ handleQueryPhoneState();
+ break;
+ case CDMA_SWAP_SECOND_CALL_STATE:
+ handleCdmaSwapSecondCallState();
+ break;
+ case CDMA_SET_SECOND_CALL_STATE:
+ handleCdmaSetSecondCallState((Boolean) msg.obj);
+ break;
+ }
+ }
+ };
+
+ private void updateBtPhoneStateAfterRadioTechnologyChange() {
+ if(VDBG) Log.d(TAG, "updateBtPhoneStateAfterRadioTechnologyChange...");
+
+ //Unregister all events from the old obsolete phone
+ mCM.getDefaultPhone().unregisterForServiceStateChanged(mHandler);
+ mCM.unregisterForPreciseCallStateChanged(mHandler);
+ mCM.unregisterForCallWaiting(mHandler);
+
+ //Register all events new to the new active phone
+ mCM.getDefaultPhone().registerForServiceStateChanged(mHandler,
+ SERVICE_STATE_CHANGED, null);
+ mCM.registerForPreciseCallStateChanged(mHandler,
+ PRECISE_CALL_STATE_CHANGED, null);
+ mCM.registerForCallWaiting(mHandler,
+ PHONE_CDMA_CALL_WAITING, null);
+ }
+
+ private void updateServiceState(ServiceState state) {
+ boolean roam = state.getRoaming();
+
+ if (roam != mRoam) {
+ mRoam = roam;
+ if (mBluetoothHeadset != null) {
+ mBluetoothHeadset.roamChanged(roam);
+ }
+ }
+ }
+
+ private void handlePreciseCallStateChange(Connection connection) {
+ // get foreground call state
+ int oldNumActive = mNumActive;
+ int oldNumHeld = mNumHeld;
+ Call.State oldRingingCallState = mRingingCallState;
+ Call.State oldForegroundCallState = mForegroundCallState;
+ CallNumber oldRingNumber = mRingNumber;
+
+ Call foregroundCall = mCM.getActiveFgCall();
+
+ if (VDBG)
+ Log.d(TAG, " handlePreciseCallStateChange: foreground: " + foregroundCall +
+ " background: " + mCM.getFirstActiveBgCall() + " ringing: " +
+ mCM.getFirstActiveRingingCall());
+
+ mForegroundCallState = foregroundCall.getState();
+ /* if in transition, do not update */
+ if (mForegroundCallState == Call.State.DISCONNECTING)
+ {
+ Log.d(TAG, "handlePreciseCallStateChange. Call disconnecting, wait before update");
+ return;
+ }
+ else
+ mNumActive = (mForegroundCallState == Call.State.ACTIVE) ? 1 : 0;
+
+ Call ringingCall = mCM.getFirstActiveRingingCall();
+ mRingingCallState = ringingCall.getState();
+ mRingNumber = getCallNumber(connection, ringingCall);
+
+ if (mCM.getDefaultPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
+ mNumHeld = getNumHeldCdma();
+ PhoneGlobals app = PhoneGlobals.getInstance();
+ if (app.cdmaPhoneCallState != null) {
+ CdmaPhoneCallState.PhoneCallState currCdmaThreeWayCallState =
+ app.cdmaPhoneCallState.getCurrentCallState();
+ CdmaPhoneCallState.PhoneCallState prevCdmaThreeWayCallState =
+ app.cdmaPhoneCallState.getPreviousCallState();
+
+ log("CDMA call state: " + currCdmaThreeWayCallState + " prev state:" +
+ prevCdmaThreeWayCallState);
+
+ if ((mBluetoothHeadset != null) &&
+ (mCdmaThreeWayCallState != currCdmaThreeWayCallState)) {
+ // In CDMA, the network does not provide any feedback
+ // to the phone when the 2nd MO call goes through the
+ // stages of DIALING > ALERTING -> ACTIVE we fake the
+ // sequence
+ log("CDMA 3way call state change. mNumActive: " + mNumActive +
+ " mNumHeld: " + mNumHeld + " IsThreeWayCallOrigStateDialing: " +
+ app.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing());
+ if ((currCdmaThreeWayCallState ==
+ CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
+ && app.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing()) {
+ // Mimic dialing, put the call on hold, alerting
+ mBluetoothHeadset.phoneStateChanged(0, mNumHeld,
+ convertCallState(Call.State.IDLE, Call.State.DIALING),
+ mRingNumber.mNumber, mRingNumber.mType);
+
+ mBluetoothHeadset.phoneStateChanged(0, mNumHeld,
+ convertCallState(Call.State.IDLE, Call.State.ALERTING),
+ mRingNumber.mNumber, mRingNumber.mType);
+
+ }
+
+ // In CDMA, the network does not provide any feedback to
+ // the phone when a user merges a 3way call or swaps
+ // between two calls we need to send a CIEV response
+ // indicating that a call state got changed which should
+ // trigger a CLCC update request from the BT client.
+ if (currCdmaThreeWayCallState ==
+ CdmaPhoneCallState.PhoneCallState.CONF_CALL &&
+ prevCdmaThreeWayCallState ==
+ CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
+ log("CDMA 3way conf call. mNumActive: " + mNumActive +
+ " mNumHeld: " + mNumHeld);
+ mBluetoothHeadset.phoneStateChanged(mNumActive, mNumHeld,
+ convertCallState(Call.State.IDLE, mForegroundCallState),
+ mRingNumber.mNumber, mRingNumber.mType);
+ }
+ }
+ mCdmaThreeWayCallState = currCdmaThreeWayCallState;
+ }
+ } else {
+ mNumHeld = getNumHeldUmts();
+ }
+
+ boolean callsSwitched = false;
+ if (mCM.getDefaultPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA &&
+ mCdmaThreeWayCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
+ callsSwitched = mCdmaCallsSwapped;
+ } else {
+ Call backgroundCall = mCM.getFirstActiveBgCall();
+ callsSwitched =
+ (mNumHeld == 1 && ! (backgroundCall.getEarliestConnectTime() ==
+ mBgndEarliestConnectionTime));
+ mBgndEarliestConnectionTime = backgroundCall.getEarliestConnectTime();
+ }
+
+ if (mNumActive != oldNumActive || mNumHeld != oldNumHeld ||
+ mRingingCallState != oldRingingCallState ||
+ mForegroundCallState != oldForegroundCallState ||
+ !mRingNumber.equalTo(oldRingNumber) ||
+ callsSwitched) {
+ if (mBluetoothHeadset != null) {
+ mBluetoothHeadset.phoneStateChanged(mNumActive, mNumHeld,
+ convertCallState(mRingingCallState, mForegroundCallState),
+ mRingNumber.mNumber, mRingNumber.mType);
+ }
+ }
+ }
+
+ private void handleListCurrentCalls() {
+ Phone phone = mCM.getDefaultPhone();
+ int phoneType = phone.getPhoneType();
+
+ // TODO(BT) handle virtual call
+
+ if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+ listCurrentCallsCdma();
+ } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
+ listCurrentCallsGsm();
+ } else {
+ Log.e(TAG, "Unexpected phone type: " + phoneType);
+ }
+ // end the result
+ // when index is 0, other parameter does not matter
+ mBluetoothHeadset.clccResponse(0, 0, 0, 0, false, "", 0);
+ }
+
+ private void handleQueryPhoneState() {
+ if (mBluetoothHeadset != null) {
+ mBluetoothHeadset.phoneStateChanged(mNumActive, mNumHeld,
+ convertCallState(mRingingCallState, mForegroundCallState),
+ mRingNumber.mNumber, mRingNumber.mType);
+ }
+ }
+
+ private int getNumHeldUmts() {
+ int countHeld = 0;
+ List<Call> heldCalls = mCM.getBackgroundCalls();
+
+ for (Call call : heldCalls) {
+ if (call.getState() == Call.State.HOLDING) {
+ countHeld++;
+ }
+ }
+ return countHeld;
+ }
+
+ private int getNumHeldCdma() {
+ int numHeld = 0;
+ PhoneGlobals app = PhoneGlobals.getInstance();
+ if (app.cdmaPhoneCallState != null) {
+ CdmaPhoneCallState.PhoneCallState curr3WayCallState =
+ app.cdmaPhoneCallState.getCurrentCallState();
+ CdmaPhoneCallState.PhoneCallState prev3WayCallState =
+ app.cdmaPhoneCallState.getPreviousCallState();
+
+ log("CDMA call state: " + curr3WayCallState + " prev state:" +
+ prev3WayCallState);
+ if (curr3WayCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
+ if (prev3WayCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
+ numHeld = 0; //0: no calls held, as now *both* the caller are active
+ } else {
+ numHeld = 1; //1: held call and active call, as on answering a
+ // Call Waiting, one of the caller *is* put on hold
+ }
+ } else if (curr3WayCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
+ numHeld = 1; //1: held call and active call, as on make a 3 Way Call
+ // the first caller *is* put on hold
+ } else {
+ numHeld = 0; //0: no calls held as this is a SINGLE_ACTIVE call
+ }
+ }
+ return numHeld;
+ }
+
+ private CallNumber getCallNumber(Connection connection, Call call) {
+ String number = null;
+ int type = 128;
+ // find phone number and type
+ if (connection == null) {
+ connection = call.getEarliestConnection();
+ if (connection == null) {
+ Log.e(TAG, "Could not get a handle on Connection object for the call");
+ }
+ }
+ if (connection != null) {
+ number = connection.getAddress();
+ if (number != null) {
+ type = PhoneNumberUtils.toaFromString(number);
+ }
+ }
+ if (number == null) {
+ number = "";
+ }
+ return new CallNumber(number, type);
+ }
+
+ private class CallNumber
+ {
+ private String mNumber = null;
+ private int mType = 0;
+
+ private CallNumber(String number, int type) {
+ mNumber = number;
+ mType = type;
+ }
+
+ private boolean equalTo(CallNumber callNumber)
+ {
+ if (mType != callNumber.mType) return false;
+
+ if (mNumber != null && mNumber.compareTo(callNumber.mNumber) == 0) {
+ return true;
+ }
+ return false;
+ }
+ }
+
+ private BluetoothProfile.ServiceListener mProfileListener =
+ new BluetoothProfile.ServiceListener() {
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ mBluetoothHeadset = (BluetoothHeadset) proxy;
+ }
+ public void onServiceDisconnected(int profile) {
+ mBluetoothHeadset = null;
+ }
+ };
+
+ private void listCurrentCallsGsm() {
+ // Collect all known connections
+ // clccConnections isindexed by CLCC index
+ Connection[] clccConnections = new Connection[GSM_MAX_CONNECTIONS];
+ LinkedList<Connection> newConnections = new LinkedList<Connection>();
+ LinkedList<Connection> connections = new LinkedList<Connection>();
+
+ Call foregroundCall = mCM.getActiveFgCall();
+ Call backgroundCall = mCM.getFirstActiveBgCall();
+ Call ringingCall = mCM.getFirstActiveRingingCall();
+
+ if (ringingCall.getState().isAlive()) {
+ connections.addAll(ringingCall.getConnections());
+ }
+ if (foregroundCall.getState().isAlive()) {
+ connections.addAll(foregroundCall.getConnections());
+ }
+ if (backgroundCall.getState().isAlive()) {
+ connections.addAll(backgroundCall.getConnections());
+ }
+
+ // Mark connections that we already known about
+ boolean clccUsed[] = new boolean[GSM_MAX_CONNECTIONS];
+ for (int i = 0; i < GSM_MAX_CONNECTIONS; i++) {
+ clccUsed[i] = mClccUsed[i];
+ mClccUsed[i] = false;
+ }
+ for (Connection c : connections) {
+ boolean found = false;
+ long timestamp = c.getCreateTime();
+ for (int i = 0; i < GSM_MAX_CONNECTIONS; i++) {
+ if (clccUsed[i] && timestamp == mClccTimestamps[i]) {
+ mClccUsed[i] = true;
+ found = true;
+ clccConnections[i] = c;
+ break;
+ }
+ }
+ if (!found) {
+ newConnections.add(c);
+ }
+ }
+
+ // Find a CLCC index for new connections
+ while (!newConnections.isEmpty()) {
+ // Find lowest empty index
+ int i = 0;
+ while (mClccUsed[i]) i++;
+ // Find earliest connection
+ long earliestTimestamp = newConnections.get(0).getCreateTime();
+ Connection earliestConnection = newConnections.get(0);
+ for (int j = 0; j < newConnections.size(); j++) {
+ long timestamp = newConnections.get(j).getCreateTime();
+ if (timestamp < earliestTimestamp) {
+ earliestTimestamp = timestamp;
+ earliestConnection = newConnections.get(j);
+ }
+ }
+
+ // update
+ mClccUsed[i] = true;
+ mClccTimestamps[i] = earliestTimestamp;
+ clccConnections[i] = earliestConnection;
+ newConnections.remove(earliestConnection);
+ }
+
+ // Send CLCC response to Bluetooth headset service
+ for (int i = 0; i < clccConnections.length; i++) {
+ if (mClccUsed[i]) {
+ sendClccResponseGsm(i, clccConnections[i]);
+ }
+ }
+ }
+
+ /** Convert a Connection object into a single +CLCC result */
+ private void sendClccResponseGsm(int index, Connection connection) {
+ int state = convertCallState(connection.getState());
+ boolean mpty = false;
+ Call call = connection.getCall();
+ if (call != null) {
+ mpty = call.isMultiparty();
+ }
+
+ int direction = connection.isIncoming() ? 1 : 0;
+
+ String number = connection.getAddress();
+ int type = -1;
+ if (number != null) {
+ type = PhoneNumberUtils.toaFromString(number);
+ }
+
+ mBluetoothHeadset.clccResponse(index + 1, direction, state, 0, mpty, number, type);
+ }
+
+ /** Build the +CLCC result for CDMA
+ * The complexity arises from the fact that we need to maintain the same
+ * CLCC index even as a call moves between states. */
+ private synchronized void listCurrentCallsCdma() {
+ // In CDMA at one time a user can have only two live/active connections
+ Connection[] clccConnections = new Connection[CDMA_MAX_CONNECTIONS];// indexed by CLCC index
+ Call foregroundCall = mCM.getActiveFgCall();
+ Call ringingCall = mCM.getFirstActiveRingingCall();
+
+ Call.State ringingCallState = ringingCall.getState();
+ // If the Ringing Call state is INCOMING, that means this is the very first call
+ // hence there should not be any Foreground Call
+ if (ringingCallState == Call.State.INCOMING) {
+ if (VDBG) log("Filling clccConnections[0] for INCOMING state");
+ clccConnections[0] = ringingCall.getLatestConnection();
+ } else if (foregroundCall.getState().isAlive()) {
+ // Getting Foreground Call connection based on Call state
+ if (ringingCall.isRinging()) {
+ if (VDBG) log("Filling clccConnections[0] & [1] for CALL WAITING state");
+ clccConnections[0] = foregroundCall.getEarliestConnection();
+ clccConnections[1] = ringingCall.getLatestConnection();
+ } else {
+ if (foregroundCall.getConnections().size() <= 1) {
+ // Single call scenario
+ if (VDBG) {
+ log("Filling clccConnections[0] with ForgroundCall latest connection");
+ }
+ clccConnections[0] = foregroundCall.getLatestConnection();
+ } else {
+ // Multiple Call scenario. This would be true for both
+ // CONF_CALL and THRWAY_ACTIVE state
+ if (VDBG) {
+ log("Filling clccConnections[0] & [1] with ForgroundCall connections");
+ }
+ clccConnections[0] = foregroundCall.getEarliestConnection();
+ clccConnections[1] = foregroundCall.getLatestConnection();
+ }
+ }
+ }
+
+ // Update the mCdmaIsSecondCallActive flag based on the Phone call state
+ if (PhoneGlobals.getInstance().cdmaPhoneCallState.getCurrentCallState()
+ == CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE) {
+ Message msg = mHandler.obtainMessage(CDMA_SET_SECOND_CALL_STATE, false);
+ mHandler.sendMessage(msg);
+ } else if (PhoneGlobals.getInstance().cdmaPhoneCallState.getCurrentCallState()
+ == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
+ Message msg = mHandler.obtainMessage(CDMA_SET_SECOND_CALL_STATE, true);
+ mHandler.sendMessage(msg);
+ }
+
+ // send CLCC result
+ for (int i = 0; (i < clccConnections.length) && (clccConnections[i] != null); i++) {
+ sendClccResponseCdma(i, clccConnections[i]);
+ }
+ }
+
+ /** Send ClCC results for a Connection object for CDMA phone */
+ private void sendClccResponseCdma(int index, Connection connection) {
+ int state;
+ PhoneGlobals app = PhoneGlobals.getInstance();
+ CdmaPhoneCallState.PhoneCallState currCdmaCallState =
+ app.cdmaPhoneCallState.getCurrentCallState();
+ CdmaPhoneCallState.PhoneCallState prevCdmaCallState =
+ app.cdmaPhoneCallState.getPreviousCallState();
+
+ if ((prevCdmaCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
+ && (currCdmaCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL)) {
+ // If the current state is reached after merging two calls
+ // we set the state of all the connections as ACTIVE
+ state = CALL_STATE_ACTIVE;
+ } else {
+ Call.State callState = connection.getState();
+ switch (callState) {
+ case ACTIVE:
+ // For CDMA since both the connections are set as active by FW after accepting
+ // a Call waiting or making a 3 way call, we need to set the state specifically
+ // to ACTIVE/HOLDING based on the mCdmaIsSecondCallActive flag. This way the
+ // CLCC result will allow BT devices to enable the swap or merge options
+ if (index == 0) { // For the 1st active connection
+ state = mCdmaIsSecondCallActive ? CALL_STATE_HELD : CALL_STATE_ACTIVE;
+ } else { // for the 2nd active connection
+ state = mCdmaIsSecondCallActive ? CALL_STATE_ACTIVE : CALL_STATE_HELD;
+ }
+ break;
+ case HOLDING:
+ state = CALL_STATE_HELD;
+ break;
+ case DIALING:
+ state = CALL_STATE_DIALING;
+ break;
+ case ALERTING:
+ state = CALL_STATE_ALERTING;
+ break;
+ case INCOMING:
+ state = CALL_STATE_INCOMING;
+ break;
+ case WAITING:
+ state = CALL_STATE_WAITING;
+ break;
+ default:
+ Log.e(TAG, "bad call state: " + callState);
+ return;
+ }
+ }
+
+ boolean mpty = false;
+ if (currCdmaCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
+ if (prevCdmaCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
+ // If the current state is reached after merging two calls
+ // we set the multiparty call true.
+ mpty = true;
+ } // else
+ // CALL_CONF state is not from merging two calls, but from
+ // accepting the second call. In this case first will be on
+ // hold in most cases but in some cases its already merged.
+ // However, we will follow the common case and the test case
+ // as per Bluetooth SIG PTS
+ }
+
+ int direction = connection.isIncoming() ? 1 : 0;
+
+ String number = connection.getAddress();
+ int type = -1;
+ if (number != null) {
+ type = PhoneNumberUtils.toaFromString(number);
+ } else {
+ number = "";
+ }
+
+ mBluetoothHeadset.clccResponse(index + 1, direction, state, 0, mpty, number, type);
+ }
+
+ private void handleCdmaSwapSecondCallState() {
+ if (VDBG) log("cdmaSwapSecondCallState: Toggling mCdmaIsSecondCallActive");
+ mCdmaIsSecondCallActive = !mCdmaIsSecondCallActive;
+ mCdmaCallsSwapped = true;
+ }
+
+ private void handleCdmaSetSecondCallState(boolean state) {
+ if (VDBG) log("cdmaSetSecondCallState: Setting mCdmaIsSecondCallActive to " + state);
+ mCdmaIsSecondCallActive = state;
+
+ if (!mCdmaIsSecondCallActive) {
+ mCdmaCallsSwapped = false;
+ }
+ }
+
+ private final IBluetoothHeadsetPhone.Stub mBinder = new IBluetoothHeadsetPhone.Stub() {
+ public boolean answerCall() {
+ enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
+ return PhoneUtils.answerCall(mCM.getFirstActiveRingingCall());
+ }
+
+ public boolean hangupCall() {
+ enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
+ if (mCM.hasActiveFgCall()) {
+ return PhoneUtils.hangupActiveCall(mCM.getActiveFgCall());
+ } else if (mCM.hasActiveRingingCall()) {
+ return PhoneUtils.hangupRingingCall(mCM.getFirstActiveRingingCall());
+ } else if (mCM.hasActiveBgCall()) {
+ return PhoneUtils.hangupHoldingCall(mCM.getFirstActiveBgCall());
+ }
+ // TODO(BT) handle virtual voice call
+ return false;
+ }
+
+ public boolean sendDtmf(int dtmf) {
+ enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
+ return mCM.sendDtmf((char) dtmf);
+ }
+
+ public boolean processChld(int chld) {
+ enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
+ Phone phone = mCM.getDefaultPhone();
+ int phoneType = phone.getPhoneType();
+ Call ringingCall = mCM.getFirstActiveRingingCall();
+ Call backgroundCall = mCM.getFirstActiveBgCall();
+
+ if (chld == CHLD_TYPE_RELEASEHELD) {
+ if (ringingCall.isRinging()) {
+ return PhoneUtils.hangupRingingCall(ringingCall);
+ } else {
+ return PhoneUtils.hangupHoldingCall(backgroundCall);
+ }
+ } else if (chld == CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD) {
+ if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+ if (ringingCall.isRinging()) {
+ // Hangup the active call and then answer call waiting call.
+ if (VDBG) log("CHLD:1 Callwaiting Answer call");
+ PhoneUtils.hangupRingingAndActive(phone);
+ } else {
+ // If there is no Call waiting then just hangup
+ // the active call. In CDMA this mean that the complete
+ // call session would be ended
+ if (VDBG) log("CHLD:1 Hangup Call");
+ PhoneUtils.hangup(PhoneGlobals.getInstance().mCM);
+ }
+ return true;
+ } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
+ // Hangup active call, answer held call
+ return PhoneUtils.answerAndEndActive(PhoneGlobals.getInstance().mCM, ringingCall);
+ } else {
+ Log.e(TAG, "bad phone type: " + phoneType);
+ return false;
+ }
+ } else if (chld == CHLD_TYPE_HOLDACTIVE_ACCEPTHELD) {
+ if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+ // For CDMA, the way we switch to a new incoming call is by
+ // calling PhoneUtils.answerCall(). switchAndHoldActive() won't
+ // properly update the call state within telephony.
+ // If the Phone state is already in CONF_CALL then we simply send
+ // a flash cmd by calling switchHoldingAndActive()
+ if (ringingCall.isRinging()) {
+ if (VDBG) log("CHLD:2 Callwaiting Answer call");
+ PhoneUtils.answerCall(ringingCall);
+ PhoneUtils.setMute(false);
+ // Setting the second callers state flag to TRUE (i.e. active)
+ cdmaSetSecondCallState(true);
+ return true;
+ } else if (PhoneGlobals.getInstance().cdmaPhoneCallState
+ .getCurrentCallState()
+ == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
+ if (VDBG) log("CHLD:2 Swap Calls");
+ PhoneUtils.switchHoldingAndActive(backgroundCall);
+ // Toggle the second callers active state flag
+ cdmaSwapSecondCallState();
+ return true;
+ }
+ Log.e(TAG, "CDMA fail to do hold active and accept held");
+ return false;
+ } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
+ PhoneUtils.switchHoldingAndActive(backgroundCall);
+ return true;
+ } else {
+ Log.e(TAG, "Unexpected phone type: " + phoneType);
+ return false;
+ }
+ } else if (chld == CHLD_TYPE_ADDHELDTOCONF) {
+ if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+ CdmaPhoneCallState.PhoneCallState state =
+ PhoneGlobals.getInstance().cdmaPhoneCallState.getCurrentCallState();
+ // For CDMA, we need to check if the call is in THRWAY_ACTIVE state
+ if (state == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
+ if (VDBG) log("CHLD:3 Merge Calls");
+ PhoneUtils.mergeCalls();
+ return true;
+ } else if (state == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
+ // State is CONF_CALL already and we are getting a merge call
+ // This can happen when CONF_CALL was entered from a Call Waiting
+ // TODO(BT)
+ return false;
+ }
+ Log.e(TAG, "GSG no call to add conference");
+ return false;
+ } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
+ if (mCM.hasActiveFgCall() && mCM.hasActiveBgCall()) {
+ PhoneUtils.mergeCalls();
+ return true;
+ } else {
+ Log.e(TAG, "GSG no call to merge");
+ return false;
+ }
+ } else {
+ Log.e(TAG, "Unexpected phone type: " + phoneType);
+ return false;
+ }
+ } else {
+ Log.e(TAG, "bad CHLD value: " + chld);
+ return false;
+ }
+ }
+
+ public String getNetworkOperator() {
+ enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
+ return mCM.getDefaultPhone().getServiceState().getOperatorAlphaLong();
+ }
+
+ public String getSubscriberNumber() {
+ enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
+ return mCM.getDefaultPhone().getLine1Number();
+ }
+
+ public boolean listCurrentCalls() {
+ enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
+ Message msg = Message.obtain(mHandler, LIST_CURRENT_CALLS);
+ mHandler.sendMessage(msg);
+ return true;
+ }
+
+ public boolean queryPhoneState() {
+ enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
+ Message msg = Message.obtain(mHandler, QUERY_PHONE_STATE);
+ mHandler.sendMessage(msg);
+ return true;
+ }
+
+ public void updateBtHandsfreeAfterRadioTechnologyChange() {
+ enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
+ if (VDBG) Log.d(TAG, "updateBtHandsfreeAfterRadioTechnologyChange...");
+ updateBtPhoneStateAfterRadioTechnologyChange();
+ }
+
+ public void cdmaSwapSecondCallState() {
+ enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
+ Message msg = Message.obtain(mHandler, CDMA_SWAP_SECOND_CALL_STATE);
+ mHandler.sendMessage(msg);
+ }
+
+ public void cdmaSetSecondCallState(boolean state) {
+ enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
+ Message msg = mHandler.obtainMessage(CDMA_SET_SECOND_CALL_STATE, state);
+ mHandler.sendMessage(msg);
+ }
+ };
+
+ // match up with bthf_call_state_t of bt_hf.h
+ final static int CALL_STATE_ACTIVE = 0;
+ final static int CALL_STATE_HELD = 1;
+ final static int CALL_STATE_DIALING = 2;
+ final static int CALL_STATE_ALERTING = 3;
+ final static int CALL_STATE_INCOMING = 4;
+ final static int CALL_STATE_WAITING = 5;
+ final static int CALL_STATE_IDLE = 6;
+
+ // match up with bthf_chld_type_t of bt_hf.h
+ final static int CHLD_TYPE_RELEASEHELD = 0;
+ final static int CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD = 1;
+ final static int CHLD_TYPE_HOLDACTIVE_ACCEPTHELD = 2;
+ final static int CHLD_TYPE_ADDHELDTOCONF = 3;
+
+ /* Convert telephony phone call state into hf hal call state */
+ static int convertCallState(Call.State ringingState, Call.State foregroundState) {
+ if ((ringingState == Call.State.INCOMING) ||
+ (ringingState == Call.State.WAITING) )
+ return CALL_STATE_INCOMING;
+ else if (foregroundState == Call.State.DIALING)
+ return CALL_STATE_DIALING;
+ else if (foregroundState == Call.State.ALERTING)
+ return CALL_STATE_ALERTING;
+ else
+ return CALL_STATE_IDLE;
+ }
+
+ static int convertCallState(Call.State callState) {
+ switch (callState) {
+ case IDLE:
+ case DISCONNECTED:
+ case DISCONNECTING:
+ return CALL_STATE_IDLE;
+ case ACTIVE:
+ return CALL_STATE_ACTIVE;
+ case HOLDING:
+ return CALL_STATE_HELD;
+ case DIALING:
+ return CALL_STATE_DIALING;
+ case ALERTING:
+ return CALL_STATE_ALERTING;
+ case INCOMING:
+ return CALL_STATE_INCOMING;
+ case WAITING:
+ return CALL_STATE_WAITING;
+ default:
+ Log.e(TAG, "bad call state: " + callState);
+ return CALL_STATE_IDLE;
+ }
+ }
+
+ private static void log(String msg) {
+ Log.d(TAG, msg);
+ }
+}
diff --git a/src/com/android/phone/CLIRListPreference.java b/src/com/android/phone/CLIRListPreference.java
index b23277a..198bdb0 100644
--- a/src/com/android/phone/CLIRListPreference.java
+++ b/src/com/android/phone/CLIRListPreference.java
@@ -20,7 +20,7 @@
*/
public class CLIRListPreference extends ListPreference {
private static final String LOG_TAG = "CLIRListPreference";
- private final boolean DBG = (PhoneApp.DBG_LEVEL >= 2);
+ private final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
private final MyHandler mHandler = new MyHandler();
private final Phone mPhone;
@@ -31,7 +31,7 @@
public CLIRListPreference(Context context, AttributeSet attrs) {
super(context, attrs);
- mPhone = PhoneApp.getPhone();
+ mPhone = PhoneGlobals.getPhone();
}
public CLIRListPreference(Context context) {
diff --git a/src/com/android/phone/CallCard.java b/src/com/android/phone/CallCard.java
index 77702d1..4c6822c 100644
--- a/src/com/android/phone/CallCard.java
+++ b/src/com/android/phone/CallCard.java
@@ -33,6 +33,7 @@
import android.text.format.DateUtils;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
@@ -61,7 +62,7 @@
implements CallTime.OnTickListener, CallerInfoAsyncQuery.OnQueryCompleteListener,
ContactsAsyncHelper.OnImageLoadCompleteListener {
private static final String LOG_TAG = "CallCard";
- private static final boolean DBG = (PhoneApp.DBG_LEVEL >= 2);
+ private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
private static final int TOKEN_UPDATE_PHOTO_FOR_CALL_STATE = 0;
private static final int TOKEN_DO_NOTHING = 1;
@@ -89,7 +90,7 @@
private InCallScreen mInCallScreen;
// Phone app instance
- private PhoneApp mApplication;
+ private PhoneGlobals mApplication;
// Top-level subviews of the CallCard
/** Container for info about the current call(s) */
@@ -181,7 +182,7 @@
if (DBG) log("- this = " + this);
if (DBG) log("- context " + context + ", attrs " + attrs);
- mApplication = PhoneApp.getInstance();
+ mApplication = PhoneGlobals.getInstance();
mCallTime = new CallTime(this);
@@ -255,8 +256,12 @@
Call fgCall = cm.getActiveFgCall();
Call bgCall = cm.getFirstActiveBgCall();
- // Update the overall layout of the onscreen elements.
- updateCallInfoLayout(state);
+ // Update the overall layout of the onscreen elements, if in PORTRAIT.
+ // Portrait uses a programatically altered layout, whereas landscape uses layout xml's.
+ // Landscape view has the views side by side, so no shifting of the picture is needed
+ if (!PhoneUtils.isLandscape(this.getContext())) {
+ updateCallInfoLayout(state);
+ }
// If the FG call is dialing/alerting, we should display for that call
// and ignore the ringing call. This case happens when the telephony
@@ -810,7 +815,7 @@
// Display "Dialing" while dialing a 3Way call, even
// though the foreground call state is actually ACTIVE.
callStateLabel = context.getString(R.string.card_title_dialing);
- } else if (PhoneApp.getInstance().notifier.getIsCdmaRedialCall()) {
+ } else if (PhoneGlobals.getInstance().notifier.getIsCdmaRedialCall()) {
callStateLabel = context.getString(R.string.card_title_redialing);
}
}
@@ -866,6 +871,14 @@
}
} else {
mCallStateLabel.setVisibility(View.GONE);
+ // Gravity is aligned left when receiving an incoming call in landscape.
+ // In that rare case, the gravity needs to be reset to the right.
+ // Also, setText("") is used since there is a delay in making the view GONE,
+ // so the user will otherwise see the text jump to the right side before disappearing.
+ if(mCallStateLabel.getGravity() != Gravity.RIGHT) {
+ mCallStateLabel.setText("");
+ mCallStateLabel.setGravity(Gravity.RIGHT);
+ }
}
if (skipAnimation) {
// Restore LayoutTransition object to recover animation.
@@ -942,7 +955,7 @@
private void displaySecondaryCallStatus(CallManager cm, Call call) {
if (DBG) log("displayOnHoldCallStatus(call =" + call + ")...");
- if ((call == null) || (PhoneApp.getInstance().isOtaCallInActiveState())) {
+ if ((call == null) || (PhoneGlobals.getInstance().isOtaCallInActiveState())) {
mSecondaryCallInfo.setVisibility(View.GONE);
return;
}
@@ -999,7 +1012,7 @@
List<Connection> connections = call.getConnections();
if (connections.size() > 2) {
// This means that current Mobile Originated call is the not the first 3-Way
- // call the user is making, which in turn tells the PhoneApp that we no
+ // call the user is making, which in turn tells the PhoneGlobals that we no
// longer know which previous caller/party had dropped out before the user
// made this call.
mSecondaryCallName.setText(
diff --git a/src/com/android/phone/CallController.java b/src/com/android/phone/CallController.java
index d9ec62d..e33e442 100644
--- a/src/com/android/phone/CallController.java
+++ b/src/com/android/phone/CallController.java
@@ -61,14 +61,14 @@
public class CallController extends Handler {
private static final String TAG = "CallController";
private static final boolean DBG =
- (PhoneApp.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
+ (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
// Do not check in with VDBG = true, since that may write PII to the system log.
private static final boolean VDBG = false;
/** The singleton CallController instance. */
private static CallController sInstance;
- private PhoneApp mApp;
+ private PhoneGlobals mApp;
private CallManager mCM;
/** Helper object for emergency calls in some rare use cases. Created lazily. */
@@ -100,7 +100,7 @@
* PhoneApp's public "callController" field, which is why there's no
* getInstance() method here.
*/
- /* package */ static CallController init(PhoneApp app) {
+ /* package */ static CallController init(PhoneGlobals app) {
synchronized (CallController.class) {
if (sInstance == null) {
sInstance = new CallController(app);
@@ -115,7 +115,7 @@
* Private constructor (this is a singleton).
* @see init()
*/
- private CallController(PhoneApp app) {
+ private CallController(PhoneGlobals app) {
if (DBG) log("CallController constructor: app = " + app);
mApp = app;
mCM = app.mCM;
diff --git a/src/com/android/phone/CallFeaturesSetting.java b/src/com/android/phone/CallFeaturesSetting.java
index 53ee8f4..ee1233d 100644
--- a/src/com/android/phone/CallFeaturesSetting.java
+++ b/src/com/android/phone/CallFeaturesSetting.java
@@ -39,6 +39,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.os.UserHandle;
import android.os.Vibrator;
import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
@@ -95,7 +96,7 @@
EditPhoneNumberPreference.OnDialogClosedListener,
EditPhoneNumberPreference.GetDefaultNumberListener{
private static final String LOG_TAG = "CallFeaturesSetting";
- private static final boolean DBG = (PhoneApp.DBG_LEVEL >= 2);
+ private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
/**
* Intent action to bring up Voicemail Provider settings.
@@ -474,8 +475,8 @@
} else if (preference == mButtonTTY) {
return true;
} else if (preference == mButtonAutoRetry) {
- android.provider.Settings.System.putInt(mPhone.getContext().getContentResolver(),
- android.provider.Settings.System.CALL_AUTO_RETRY,
+ android.provider.Settings.Global.putInt(mPhone.getContext().getContentResolver(),
+ android.provider.Settings.Global.CALL_AUTO_RETRY,
mButtonAutoRetry.isChecked() ? 1 : 0);
return true;
} else if (preference == mButtonHAC) {
@@ -1482,7 +1483,7 @@
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
if (DBG) log("onCreate(). Intent: " + getIntent());
- mPhone = PhoneApp.getPhone();
+ mPhone = PhoneGlobals.getPhone();
addPreferencesFromResource(R.xml.call_feature_setting);
@@ -1725,8 +1726,8 @@
}
if (mButtonAutoRetry != null) {
- int autoretry = Settings.System.getInt(getContentResolver(),
- Settings.System.CALL_AUTO_RETRY, 0);
+ int autoretry = Settings.Global.getInt(getContentResolver(),
+ Settings.Global.CALL_AUTO_RETRY, 0);
mButtonAutoRetry.setChecked(autoretry != 0);
}
@@ -1799,7 +1800,7 @@
updatePreferredTtyModeSummary(buttonTtyMode);
Intent ttyModeChanged = new Intent(TtyIntent.TTY_PREFERRED_MODE_CHANGE_ACTION);
ttyModeChanged.putExtra(TtyIntent.TTY_PREFFERED_MODE, buttonTtyMode);
- sendBroadcast(ttyModeChanged);
+ sendBroadcastAsUser(ttyModeChanged, UserHandle.ALL);
}
}
diff --git a/src/com/android/phone/CallForwardEditPreference.java b/src/com/android/phone/CallForwardEditPreference.java
index 544d4c2..f925022 100644
--- a/src/com/android/phone/CallForwardEditPreference.java
+++ b/src/com/android/phone/CallForwardEditPreference.java
@@ -21,7 +21,7 @@
public class CallForwardEditPreference extends EditPhoneNumberPreference {
private static final String LOG_TAG = "CallForwardEditPreference";
- private static final boolean DBG = (PhoneApp.DBG_LEVEL >= 2);
+ private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
private static final String SRC_TAGS[] = {"{0}"};
private CharSequence mSummaryOnTemplate;
@@ -43,7 +43,7 @@
public CallForwardEditPreference(Context context, AttributeSet attrs) {
super(context, attrs);
- phone = PhoneApp.getPhone();
+ phone = PhoneGlobals.getPhone();
mSummaryOnTemplate = this.getSummaryOn();
TypedArray a = context.obtainStyledAttributes(attrs,
diff --git a/src/com/android/phone/CallNotifier.java b/src/com/android/phone/CallNotifier.java
index dee75de..99636df 100755
--- a/src/com/android/phone/CallNotifier.java
+++ b/src/com/android/phone/CallNotifier.java
@@ -31,6 +31,9 @@
import com.android.internal.telephony.cdma.SignalToneUtil;
import android.app.ActivityManagerNative;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.media.AudioManager;
import android.media.ToneGenerator;
@@ -61,8 +64,8 @@
implements CallerInfoAsyncQuery.OnQueryCompleteListener {
private static final String LOG_TAG = "CallNotifier";
private static final boolean DBG =
- (PhoneApp.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
- private static final boolean VDBG = (PhoneApp.DBG_LEVEL >= 2);
+ (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
+ private static final boolean VDBG = (PhoneGlobals.DBG_LEVEL >= 2);
// Maximum time we allow the CallerInfo query to run,
// before giving up and falling back to the default ringtone.
@@ -148,10 +151,10 @@
private static final int EMERGENCY_TONE_ALERT = 1;
private static final int EMERGENCY_TONE_VIBRATE = 2;
- private PhoneApp mApplication;
+ private PhoneGlobals mApplication;
private CallManager mCM;
private Ringer mRinger;
- private BluetoothHandsfree mBluetoothHandsfree;
+ private BluetoothHeadset mBluetoothHeadset;
private CallLogAsync mCallLog;
private boolean mSilentRingerRequested;
@@ -183,11 +186,11 @@
* Initialize the singleton CallNotifier instance.
* This is only done once, at startup, from PhoneApp.onCreate().
*/
- /* package */ static CallNotifier init(PhoneApp app, Phone phone, Ringer ringer,
- BluetoothHandsfree btMgr, CallLogAsync callLog) {
+ /* package */ static CallNotifier init(PhoneGlobals app, Phone phone, Ringer ringer,
+ CallLogAsync callLog) {
synchronized (CallNotifier.class) {
if (sInstance == null) {
- sInstance = new CallNotifier(app, phone, ringer, btMgr, callLog);
+ sInstance = new CallNotifier(app, phone, ringer, callLog);
} else {
Log.wtf(LOG_TAG, "init() called multiple times! sInstance = " + sInstance);
}
@@ -196,8 +199,7 @@
}
/** Private constructor; @see init() */
- private CallNotifier(PhoneApp app, Phone phone, Ringer ringer,
- BluetoothHandsfree btMgr, CallLogAsync callLog) {
+ private CallNotifier(PhoneGlobals app, Phone phone, Ringer ringer, CallLogAsync callLog) {
mApplication = app;
mCM = app.mCM;
mCallLog = callLog;
@@ -220,7 +222,12 @@
}
mRinger = ringer;
- mBluetoothHandsfree = btMgr;
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ if (adapter != null) {
+ adapter.getProfileProxy(mApplication.getApplicationContext(),
+ mBluetoothProfileServiceListener,
+ BluetoothProfile.HEADSET);
+ }
TelephonyManager telephonyManager = (TelephonyManager)app.getSystemService(
Context.TELEPHONY_SERVICE);
@@ -435,7 +442,7 @@
// (This will be upgraded soon to a full wake lock; see
// showIncomingCall().)
if (VDBG) log("Holding wake lock on new incoming connection.");
- mApplication.requestWakeState(PhoneApp.WakeState.PARTIAL);
+ mApplication.requestWakeState(PhoneGlobals.WakeState.PARTIAL);
// - don't ring for call waiting connections
// - do this before showing the incoming call panel
@@ -484,7 +491,7 @@
*/
private boolean ignoreAllIncomingCalls(Phone phone) {
// Incoming calls are totally ignored on non-voice-capable devices.
- if (!PhoneApp.sVoiceCapable) {
+ if (!PhoneGlobals.sVoiceCapable) {
// ...but still log a warning, since we shouldn't have gotten this
// event in the first place! (Incoming calls *should* be blocked at
// the telephony layer on non-voice-capable capable devices.)
@@ -501,8 +508,8 @@
}
// Incoming calls are totally ignored if the device isn't provisioned yet.
- boolean provisioned = Settings.Secure.getInt(mApplication.getContentResolver(),
- Settings.Secure.DEVICE_PROVISIONED, 0) != 0;
+ boolean provisioned = Settings.Global.getInt(mApplication.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 0) != 0;
if (!provisioned) {
Log.i(LOG_TAG, "Ignoring incoming call: not provisioned");
return true;
@@ -701,34 +708,7 @@
// Go directly to the in-call screen.
// (No need to do anything special if we're already on the in-call
// screen; it'll notice the phone state change and update itself.)
-
- // But first, grab a full wake lock. We do this here, before we
- // even fire off the InCallScreen intent, to make sure the
- // ActivityManager doesn't try to pause the InCallScreen as soon
- // as it comes up. (See bug 1648751.)
- //
- // And since the InCallScreen isn't visible yet (we haven't even
- // fired off the intent yet), we DON'T want the screen to actually
- // come on right now. So *before* acquiring the wake lock we need
- // to call preventScreenOn(), which tells the PowerManager that
- // the screen should stay off even if someone's holding a full
- // wake lock. (This prevents any flicker during the "incoming
- // call" sequence. The corresponding preventScreenOn(false) call
- // will come from the InCallScreen when it's finally ready to be
- // displayed.)
- //
- // TODO: this is all a temporary workaround. The real fix is to add
- // an Activity attribute saying "this Activity wants to wake up the
- // phone when it's displayed"; that way the ActivityManager could
- // manage the wake locks *and* arrange for the screen to come on at
- // the exact moment that the InCallScreen is ready to be displayed.
- // (See bug 1648751.)
- //
- // TODO: also, we should probably *not* do any of this if the
- // screen is already on(!)
-
- mApplication.preventScreenOn(true);
- mApplication.requestWakeState(PhoneApp.WakeState.FULL);
+ mApplication.requestWakeState(PhoneGlobals.WakeState.FULL);
// Post the "incoming call" notification *and* include the
// fullScreenIntent that'll launch the incoming-call UI.
@@ -797,8 +777,7 @@
// if the call screen is showing, let it handle the event,
// otherwise handle it here.
if (!mApplication.isShowingCallScreen()) {
- mApplication.setScreenTimeout(PhoneApp.ScreenTimeoutDuration.DEFAULT);
- mApplication.requestWakeState(PhoneApp.WakeState.SLEEP);
+ mApplication.requestWakeState(PhoneGlobals.WakeState.SLEEP);
}
// Since we're now in-call, the Ringer should definitely *not*
@@ -842,9 +821,9 @@
}
if (callState == Call.State.DIALING || callState == Call.State.ALERTING) {
- mIsEmergencyToneOn = Settings.System.getInt(
+ mIsEmergencyToneOn = Settings.Global.getInt(
mApplication.getContentResolver(),
- Settings.System.EMERGENCY_TONE, EMERGENCY_TONE_OFF);
+ Settings.Global.EMERGENCY_TONE, EMERGENCY_TONE_OFF);
if (mIsEmergencyToneOn != EMERGENCY_TONE_OFF &&
mCurrentEmergencyToneState == EMERGENCY_TONE_OFF) {
if (mEmergencyTonePlayerVibrator != null) {
@@ -1033,8 +1012,8 @@
int autoretrySetting = 0;
if ((c != null) && (c.getCall().getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA)) {
- autoretrySetting = android.provider.Settings.System.getInt(mApplication.
- getContentResolver(),android.provider.Settings.System.CALL_AUTO_RETRY, 0);
+ autoretrySetting = android.provider.Settings.Global.getInt(mApplication.
+ getContentResolver(),android.provider.Settings.Global.CALL_AUTO_RETRY, 0);
}
// Stop any signalInfo tone being played when a call gets ended
@@ -1160,14 +1139,6 @@
}
mApplication.notificationMgr.cancelCallInProgressNotifications();
-
- // If the screen is turned off when all the phone calls are hung up,
- // InCallScreen#onDisconnect() will wake up the screen (only once) and let users
- // check the disconnected status.
- if (mApplication.isShowingCallScreen()) {
- if (VDBG) log("onDisconnect: In call screen. Set short timeout.");
- mApplication.clearUserActivityTimeout();
- }
}
if (c != null) {
@@ -1292,8 +1263,8 @@
private void resetAudioStateAfterDisconnect() {
if (VDBG) log("resetAudioStateAfterDisconnect()...");
- if (mBluetoothHandsfree != null) {
- mBluetoothHandsfree.audioOff();
+ if (mBluetoothHeadset != null) {
+ mBluetoothHeadset.disconnectAudio();
}
// call turnOnSpeaker() with state=false and store=true even if speaker
@@ -1308,7 +1279,7 @@
// "Voicemail" is meaningless on non-voice-capable devices,
// so ignore MWI events.
- if (!PhoneApp.sVoiceCapable) {
+ if (!PhoneGlobals.sVoiceCapable) {
// ...but still log a warning, since we shouldn't have gotten this
// event in the first place!
// (PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR events
@@ -1528,8 +1499,8 @@
ToneGenerator toneGenerator;
try {
int stream;
- if (mBluetoothHandsfree != null) {
- stream = mBluetoothHandsfree.isAudioOn() ? AudioManager.STREAM_BLUETOOTH_SCO:
+ if (mBluetoothHeadset != null) {
+ stream = mBluetoothHeadset.isAudioOn() ? AudioManager.STREAM_BLUETOOTH_SCO:
AudioManager.STREAM_VOICE_CALL;
} else {
stream = AudioManager.STREAM_VOICE_CALL;
@@ -1694,7 +1665,7 @@
*/
private void onSignalInfo(AsyncResult r) {
// Signal Info are totally ignored on non-voice-capable devices.
- if (!PhoneApp.sVoiceCapable) {
+ if (!PhoneGlobals.sVoiceCapable) {
Log.w(LOG_TAG, "Got onSignalInfo() on non-voice-capable device! Ignoring...");
return;
}
@@ -1995,6 +1966,18 @@
}
}
+ private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
+ new BluetoothProfile.ServiceListener() {
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ mBluetoothHeadset = (BluetoothHeadset) proxy;
+ if (VDBG) log("- Got BluetoothHeadset: " + mBluetoothHeadset);
+ }
+
+ public void onServiceDisconnected(int profile) {
+ mBluetoothHeadset = null;
+ }
+ };
+
private void onRingbackTone(AsyncResult r) {
boolean playTone = (Boolean)(r.result);
diff --git a/src/com/android/phone/CallTime.java b/src/com/android/phone/CallTime.java
index 39a69af..92c7972 100644
--- a/src/com/android/phone/CallTime.java
+++ b/src/com/android/phone/CallTime.java
@@ -195,7 +195,7 @@
// For now, we move away from temp directory in favor of
// the application's data directory to store the trace
// information (/data/data/com.android.phone).
- File file = PhoneApp.getInstance().getDir ("phoneTrace", Context.MODE_PRIVATE);
+ File file = PhoneGlobals.getInstance().getDir ("phoneTrace", Context.MODE_PRIVATE);
if (file.exists() == false) {
file.mkdirs();
}
diff --git a/src/com/android/phone/CallWaitingCheckBoxPreference.java b/src/com/android/phone/CallWaitingCheckBoxPreference.java
index 6394ff1..a2f5c70 100644
--- a/src/com/android/phone/CallWaitingCheckBoxPreference.java
+++ b/src/com/android/phone/CallWaitingCheckBoxPreference.java
@@ -17,7 +17,7 @@
public class CallWaitingCheckBoxPreference extends CheckBoxPreference {
private static final String LOG_TAG = "CallWaitingCheckBoxPreference";
- private final boolean DBG = (PhoneApp.DBG_LEVEL >= 2);
+ private final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
private final MyHandler mHandler = new MyHandler();
private final Phone mPhone;
@@ -26,7 +26,7 @@
public CallWaitingCheckBoxPreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
- mPhone = PhoneApp.getPhone();
+ mPhone = PhoneGlobals.getPhone();
}
public CallWaitingCheckBoxPreference(Context context, AttributeSet attrs) {
diff --git a/src/com/android/phone/CallerInfoCache.java b/src/com/android/phone/CallerInfoCache.java
index dc6ffae..76f79af 100644
--- a/src/com/android/phone/CallerInfoCache.java
+++ b/src/com/android/phone/CallerInfoCache.java
@@ -45,7 +45,7 @@
public class CallerInfoCache {
private static final String LOG_TAG = CallerInfoCache.class.getSimpleName();
private static final boolean DBG =
- (PhoneApp.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
+ (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
/** This must not be set to true when submitting changes. */
private static final boolean VDBG = false;
diff --git a/src/com/android/phone/CallerInfoCacheUpdateReceiver.java b/src/com/android/phone/CallerInfoCacheUpdateReceiver.java
index 5d60947..c0a2d83 100644
--- a/src/com/android/phone/CallerInfoCacheUpdateReceiver.java
+++ b/src/com/android/phone/CallerInfoCacheUpdateReceiver.java
@@ -31,7 +31,7 @@
public class CallerInfoCacheUpdateReceiver extends BroadcastReceiver {
private static final String LOG_TAG = CallerInfoCacheUpdateReceiver.class.getSimpleName();
private static final boolean DBG =
- (PhoneApp.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
+ (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
public static final String ACTION_UPDATE_CALLER_INFO_CACHE =
"com.android.phone.UPDATE_CALLER_INFO_CACHE";
@@ -39,7 +39,7 @@
@Override
public void onReceive(Context context, Intent intent) {
if (DBG) log("CallerInfoCacheUpdateReceiver#onReceive(). Intent: " + intent);
- PhoneApp.getInstance().callerInfoCache.startAsyncCache();
+ PhoneGlobals.getInstance().callerInfoCache.startAsyncCache();
}
private static void log(String msg) {
diff --git a/src/com/android/phone/CdmaCallOptions.java b/src/com/android/phone/CdmaCallOptions.java
index 656863b..8eecd27 100644
--- a/src/com/android/phone/CdmaCallOptions.java
+++ b/src/com/android/phone/CdmaCallOptions.java
@@ -32,7 +32,7 @@
public class CdmaCallOptions extends PreferenceActivity {
private static final String LOG_TAG = "CdmaCallOptions";
- private final boolean DBG = (PhoneApp.DBG_LEVEL >= 2);
+ private final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
private static final String BUTTON_VP_KEY = "button_voice_privacy_key";
private CheckBoxPreference mButtonVoicePrivacy;
@@ -44,7 +44,7 @@
addPreferencesFromResource(R.xml.cdma_call_privacy);
mButtonVoicePrivacy = (CheckBoxPreference) findPreference(BUTTON_VP_KEY);
- if (PhoneApp.getPhone().getPhoneType() != PhoneConstants.PHONE_TYPE_CDMA
+ if (PhoneGlobals.getPhone().getPhoneType() != PhoneConstants.PHONE_TYPE_CDMA
|| getResources().getBoolean(R.bool.config_voice_privacy_disable)) {
//disable the entire screen
getPreferenceScreen().setEnabled(false);
diff --git a/src/com/android/phone/CdmaDisplayInfo.java b/src/com/android/phone/CdmaDisplayInfo.java
index 1e7dee8..1a88333 100755
--- a/src/com/android/phone/CdmaDisplayInfo.java
+++ b/src/com/android/phone/CdmaDisplayInfo.java
@@ -63,7 +63,7 @@
WindowManager.LayoutParams.FLAG_DIM_BEHIND);
sDisplayInfoDialog.show();
- PhoneApp.getInstance().wakeUpScreen();
+ PhoneGlobals.getInstance().wakeUpScreen();
}
diff --git a/src/com/android/phone/CdmaSubscriptionListPreference.java b/src/com/android/phone/CdmaSubscriptionListPreference.java
index 43d3c0b..9b96850 100644
--- a/src/com/android/phone/CdmaSubscriptionListPreference.java
+++ b/src/com/android/phone/CdmaSubscriptionListPreference.java
@@ -22,6 +22,7 @@
import android.os.Handler;
import android.os.Message;
import android.preference.ListPreference;
+import android.provider.Settings;
import android.provider.Settings.Secure;
import android.util.AttributeSet;
import android.util.Log;
@@ -53,8 +54,8 @@
}
private void setCurrentCdmaSubscriptionModeValue() {
- int cdmaSubscriptionMode = Secure.getInt(mPhone.getContext().getContentResolver(),
- android.provider.Settings.Secure.CDMA_SUBSCRIPTION_MODE, preferredSubscriptionMode);
+ int cdmaSubscriptionMode = Settings.Global.getInt(mPhone.getContext().getContentResolver(),
+ Settings.Global.CDMA_SUBSCRIPTION_MODE, preferredSubscriptionMode);
setValue(Integer.toString(cdmaSubscriptionMode));
}
@@ -120,8 +121,8 @@
if (ar.exception == null) {
// Get the original string entered by the user
int cdmaSubscriptionMode = Integer.valueOf((String) ar.userObj).intValue();
- Secure.putInt(mPhone.getContext().getContentResolver(),
- Secure.CDMA_SUBSCRIPTION_MODE,
+ Settings.Global.putInt(mPhone.getContext().getContentResolver(),
+ Settings.Global.CDMA_SUBSCRIPTION_MODE,
cdmaSubscriptionMode );
} else {
Log.e(LOG_TAG, "Setting Cdma subscription source failed");
diff --git a/src/com/android/phone/CdmaSystemSelectListPreference.java b/src/com/android/phone/CdmaSystemSelectListPreference.java
index 7bbe008..d291fd7 100644
--- a/src/com/android/phone/CdmaSystemSelectListPreference.java
+++ b/src/com/android/phone/CdmaSystemSelectListPreference.java
@@ -23,6 +23,7 @@
import android.os.Handler;
import android.os.Message;
import android.preference.ListPreference;
+import android.provider.Settings;
import android.provider.Settings.Secure;
import android.util.AttributeSet;
import android.util.Log;
@@ -41,7 +42,7 @@
public CdmaSystemSelectListPreference(Context context, AttributeSet attrs) {
super(context, attrs);
- mPhone = PhoneApp.getPhone();
+ mPhone = PhoneGlobals.getPhone();
mHandler = new MyHandler();
mPhone.queryCdmaRoamingPreference(
mHandler.obtainMessage(MyHandler.MESSAGE_GET_ROAMING_PREFERENCE));
@@ -68,8 +69,8 @@
if (positiveResult && (getValue() != null)) {
int buttonCdmaRoamingMode = Integer.valueOf(getValue()).intValue();
int settingsCdmaRoamingMode =
- Secure.getInt(mPhone.getContext().getContentResolver(),
- Secure.CDMA_ROAMING_MODE, Phone.CDMA_RM_HOME);
+ Settings.Global.getInt(mPhone.getContext().getContentResolver(),
+ Settings.Global.CDMA_ROAMING_MODE, Phone.CDMA_RM_HOME);
if (buttonCdmaRoamingMode != settingsCdmaRoamingMode) {
int statusCdmaRoamingMode;
switch(buttonCdmaRoamingMode) {
@@ -81,8 +82,8 @@
statusCdmaRoamingMode = Phone.CDMA_RM_HOME;
}
//Set the Settings.Secure network mode
- Secure.putInt(mPhone.getContext().getContentResolver(),
- Secure.CDMA_ROAMING_MODE,
+ Settings.Global.putInt(mPhone.getContext().getContentResolver(),
+ Settings.Global.CDMA_ROAMING_MODE,
buttonCdmaRoamingMode );
//Set the roaming preference mode
mPhone.setCdmaRoamingPreference(statusCdmaRoamingMode, mHandler
@@ -117,9 +118,9 @@
if (ar.exception == null) {
int statusCdmaRoamingMode = ((int[])ar.result)[0];
- int settingsRoamingMode = Secure.getInt(
+ int settingsRoamingMode = Settings.Global.getInt(
mPhone.getContext().getContentResolver(),
- Secure.CDMA_ROAMING_MODE, Phone.CDMA_RM_HOME);
+ Settings.Global.CDMA_ROAMING_MODE, Phone.CDMA_RM_HOME);
//check that statusCdmaRoamingMode is from an accepted value
if (statusCdmaRoamingMode == Phone.CDMA_RM_HOME ||
statusCdmaRoamingMode == Phone.CDMA_RM_ANY ) {
@@ -127,9 +128,9 @@
if (statusCdmaRoamingMode != settingsRoamingMode) {
settingsRoamingMode = statusCdmaRoamingMode;
//changes the Settings.Secure accordingly to statusCdmaRoamingMode
- Secure.putInt(
+ Settings.Global.putInt(
mPhone.getContext().getContentResolver(),
- Secure.CDMA_ROAMING_MODE,
+ Settings.Global.CDMA_ROAMING_MODE,
settingsRoamingMode );
}
//changes the mButtonPreferredNetworkMode accordingly to modemNetworkMode
@@ -147,8 +148,8 @@
if ((ar.exception == null) && (getValue() != null)) {
int cdmaRoamingMode = Integer.valueOf(getValue()).intValue();
- Secure.putInt(mPhone.getContext().getContentResolver(),
- Secure.CDMA_ROAMING_MODE,
+ Settings.Global.putInt(mPhone.getContext().getContentResolver(),
+ Settings.Global.CDMA_ROAMING_MODE,
cdmaRoamingMode );
} else {
mPhone.queryCdmaRoamingPreference(obtainMessage(MESSAGE_GET_ROAMING_PREFERENCE));
@@ -159,8 +160,8 @@
//set the mButtonCdmaRoam
setValue(Integer.toString(Phone.CDMA_RM_HOME));
//set the Settings.System
- Secure.putInt(mPhone.getContext().getContentResolver(),
- Secure.CDMA_ROAMING_MODE,
+ Settings.Global.putInt(mPhone.getContext().getContentResolver(),
+ Settings.Global.CDMA_ROAMING_MODE,
Phone.CDMA_RM_HOME );
//Set the Status
mPhone.setCdmaRoamingPreference(Phone.CDMA_RM_HOME,
diff --git a/src/com/android/phone/CdmaVoicePrivacyCheckBoxPreference.java b/src/com/android/phone/CdmaVoicePrivacyCheckBoxPreference.java
index 6439c67..a5ff37e 100644
--- a/src/com/android/phone/CdmaVoicePrivacyCheckBoxPreference.java
+++ b/src/com/android/phone/CdmaVoicePrivacyCheckBoxPreference.java
@@ -28,7 +28,7 @@
public class CdmaVoicePrivacyCheckBoxPreference extends CheckBoxPreference {
private static final String LOG_TAG = "CdmaVoicePrivacyCheckBoxPreference";
- private final boolean DBG = (PhoneApp.DBG_LEVEL >= 2);
+ private final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
Phone phone;
private MyHandler mHandler = new MyHandler();
@@ -36,7 +36,7 @@
public CdmaVoicePrivacyCheckBoxPreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
- phone = PhoneApp.getPhone();
+ phone = PhoneGlobals.getPhone();
phone.getEnhancedVoicePrivacy(mHandler.obtainMessage(MyHandler.MESSAGE_GET_VP));
}
diff --git a/src/com/android/phone/CellBroadcastSms.java b/src/com/android/phone/CellBroadcastSms.java
index 296fb69..7428321 100644
--- a/src/com/android/phone/CellBroadcastSms.java
+++ b/src/com/android/phone/CellBroadcastSms.java
@@ -183,15 +183,15 @@
if(mButtonBcSms.isChecked()) {
mPhone.activateCellBroadcastSms(RILConstants.CDMA_CELL_BROADCAST_SMS_ENABLED,
Message.obtain(mHandler, MESSAGE_ACTIVATE_CB_SMS));
- android.provider.Settings.Secure.putInt(mPhone.getContext().getContentResolver(),
- android.provider.Settings.Secure.CDMA_CELL_BROADCAST_SMS,
+ android.provider.Settings.Global.putInt(mPhone.getContext().getContentResolver(),
+ android.provider.Settings.Global.CDMA_CELL_BROADCAST_SMS,
RILConstants.CDMA_CELL_BROADCAST_SMS_ENABLED);
enableDisableAllCbConfigButtons(true);
} else {
mPhone.activateCellBroadcastSms(RILConstants.CDMA_CELL_BROADCAST_SMS_DISABLED,
Message.obtain(mHandler, MESSAGE_ACTIVATE_CB_SMS));
- android.provider.Settings.Secure.putInt(mPhone.getContext().getContentResolver(),
- android.provider.Settings.Secure.CDMA_CELL_BROADCAST_SMS,
+ android.provider.Settings.Global.putInt(mPhone.getContext().getContentResolver(),
+ android.provider.Settings.Global.CDMA_CELL_BROADCAST_SMS,
RILConstants.CDMA_CELL_BROADCAST_SMS_DISABLED);
enableDisableAllCbConfigButtons(false);
}
@@ -337,7 +337,7 @@
addPreferencesFromResource(R.xml.cell_broadcast_sms);
- mPhone = PhoneApp.getPhone();
+ mPhone = PhoneGlobals.getPhone();
mHandler = new MyHandler();
PreferenceScreen prefSet = getPreferenceScreen();
@@ -422,9 +422,9 @@
getPreferenceScreen().setEnabled(true);
- int settingCbSms = android.provider.Settings.Secure.getInt(
+ int settingCbSms = android.provider.Settings.Global.getInt(
mPhone.getContext().getContentResolver(),
- android.provider.Settings.Secure.CDMA_CELL_BROADCAST_SMS,
+ android.provider.Settings.Global.CDMA_CELL_BROADCAST_SMS,
RILConstants.CDMA_CELL_BROADCAST_SMS_DISABLED);
mButtonBcSms.setChecked(settingCbSms == RILConstants.CDMA_CELL_BROADCAST_SMS_ENABLED);
@@ -547,8 +547,8 @@
mButtonBcSms.setChecked(false);
mPhone.activateCellBroadcastSms(RILConstants.CDMA_CELL_BROADCAST_SMS_DISABLED,
Message.obtain(mHandler, MESSAGE_ACTIVATE_CB_SMS));
- android.provider.Settings.Secure.putInt(mPhone.getContext().getContentResolver(),
- android.provider.Settings.Secure.CDMA_CELL_BROADCAST_SMS,
+ android.provider.Settings.Global.putInt(mPhone.getContext().getContentResolver(),
+ android.provider.Settings.Global.CDMA_CELL_BROADCAST_SMS,
RILConstants.CDMA_CELL_BROADCAST_SMS_DISABLED);
enableDisableAllCbConfigButtons(false);
}
diff --git a/src/com/android/phone/ChangeIccPinScreen.java b/src/com/android/phone/ChangeIccPinScreen.java
index 7a77294..70bf431 100644
--- a/src/com/android/phone/ChangeIccPinScreen.java
+++ b/src/com/android/phone/ChangeIccPinScreen.java
@@ -42,7 +42,7 @@
* "Change ICC PIN" UI for the Phone app.
*/
public class ChangeIccPinScreen extends Activity {
- private static final String LOG_TAG = PhoneApp.LOG_TAG;
+ private static final String LOG_TAG = PhoneGlobals.LOG_TAG;
private static final boolean DBG = false;
private static final int EVENT_PIN_CHANGED = 100;
@@ -91,7 +91,7 @@
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
- mPhone = PhoneApp.getPhone();
+ mPhone = PhoneGlobals.getPhone();
resolveIntent();
diff --git a/src/com/android/phone/ClearMissedCallsService.java b/src/com/android/phone/ClearMissedCallsService.java
index c9608b8..b882472 100644
--- a/src/com/android/phone/ClearMissedCallsService.java
+++ b/src/com/android/phone/ClearMissedCallsService.java
@@ -29,7 +29,7 @@
public static final String ACTION_CLEAR_MISSED_CALLS =
"com.android.phone.intent.CLEAR_MISSED_CALLS";
- private PhoneApp mApp;
+ private PhoneGlobals mApp;
public ClearMissedCallsService() {
super(ClearMissedCallsService.class.getSimpleName());
@@ -38,7 +38,7 @@
@Override
public void onCreate() {
super.onCreate();
- mApp = PhoneApp.getInstance();
+ mApp = PhoneGlobals.getInstance();
}
@Override
diff --git a/src/com/android/phone/DTMFTwelveKeyDialer.java b/src/com/android/phone/DTMFTwelveKeyDialer.java
index c47b366..0f198f2 100644
--- a/src/com/android/phone/DTMFTwelveKeyDialer.java
+++ b/src/com/android/phone/DTMFTwelveKeyDialer.java
@@ -16,6 +16,7 @@
package com.android.phone;
+import android.content.Context;
import android.media.AudioManager;
import android.media.ToneGenerator;
import android.os.Handler;
@@ -30,6 +31,9 @@
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.View.OnHoverListener;
+import android.view.accessibility.AccessibilityManager;
import android.view.ViewStub;
import android.widget.EditText;
@@ -46,14 +50,18 @@
* Dialer class that encapsulates the DTMF twelve key behaviour.
* This model backs up the UI behaviour in DTMFTwelveKeyDialerView.java.
*/
-public class DTMFTwelveKeyDialer implements View.OnTouchListener, View.OnKeyListener {
+public class DTMFTwelveKeyDialer implements View.OnTouchListener, View.OnKeyListener,
+ View.OnHoverListener, View.OnClickListener {
private static final String LOG_TAG = "DTMFTwelveKeyDialer";
- private static final boolean DBG = (PhoneApp.DBG_LEVEL >= 2);
+ private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
// events
private static final int PHONE_DISCONNECT = 100;
private static final int DTMF_SEND_CNF = 101;
+ /** Accessibility manager instance used to check touch exploration state. */
+ private final AccessibilityManager mAccessibilityManager;
+
private CallManager mCM;
private ToneGenerator mToneGenerator;
private final Object mToneGeneratorLock = new Object();
@@ -366,10 +374,7 @@
*/
public DTMFTwelveKeyDialer(InCallScreen parent,
DTMFTwelveKeyDialerView dialerView) {
- if (DBG) log("DTMFTwelveKeyDialer constructor... this = " + this);
-
- mInCallScreen = parent;
- mCM = PhoneApp.getInstance().mCM;
+ this(parent);
// The passed-in DTMFTwelveKeyDialerView *should* always be
// non-null, now that the in-call UI uses only portrait mode.
@@ -398,10 +403,7 @@
* {@link ViewStub#inflate()}.
*/
public DTMFTwelveKeyDialer(InCallScreen parent, ViewStub dialerStub) {
- if (DBG) log("DTMFTwelveKeyDialer constructor... this = " + this);
-
- mInCallScreen = parent;
- mCM = PhoneApp.getInstance().mCM;
+ this(parent);
mDialerStub = dialerStub;
if (DBG) log("- Got passed-in mDialerStub: " + mDialerStub);
@@ -410,6 +412,21 @@
}
/**
+ * Private constructor used for initialization calls common to all public
+ * constructors.
+ *
+ * @param parent the InCallScreen instance that owns us.
+ */
+ private DTMFTwelveKeyDialer(InCallScreen parent) {
+ if (DBG) log("DTMFTwelveKeyDialer constructor... this = " + this);
+
+ mInCallScreen = parent;
+ mCM = PhoneGlobals.getInstance().mCM;
+ mAccessibilityManager = (AccessibilityManager) parent.getSystemService(
+ Context.ACCESSIBILITY_SERVICE);
+ }
+
+ /**
* Prepare the dialer view and relevant variables.
*/
private void setupDialerView() {
@@ -467,7 +484,7 @@
// On some devices the screen timeout is set to a special value
// while the dialpad is up.
- PhoneApp.getInstance().updateWakeState();
+ PhoneGlobals.getInstance().updateWakeState();
// Give the InCallScreen a chance to do any necessary UI updates.
if (mInCallScreen != null) {
@@ -493,7 +510,7 @@
if (DBG) log("startDialerSession()... this = " + this);
// see if we need to play local tones.
- if (PhoneApp.getInstance().getResources().getBoolean(R.bool.allow_local_dtmf_tones)) {
+ if (PhoneGlobals.getInstance().getResources().getBoolean(R.bool.allow_local_dtmf_tones)) {
mLocalToneEnabled = Settings.System.getInt(mInCallScreen.getContentResolver(),
Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;
} else {
@@ -526,7 +543,7 @@
if (DBG) log("onDialerClose()...");
// reset back to a short delay for the poke lock.
- PhoneApp app = PhoneApp.getInstance();
+ PhoneGlobals app = PhoneGlobals.getInstance();
app.updateWakeState();
mCM.unregisterForDisconnect(mHandler);
@@ -593,6 +610,8 @@
button.setOnTouchListener(this);
button.setClickable(true);
button.setOnKeyListener(this);
+ button.setOnHoverListener(this);
+ button.setOnClickListener(this);
}
}
@@ -621,6 +640,53 @@
}
/**
+ * Implemented for {@link android.view.View.OnHoverListener}. Handles touch
+ * events for accessibility when touch exploration is enabled.
+ */
+ @Override
+ public boolean onHover(View v, MotionEvent event) {
+ // When touch exploration is turned on, lifting a finger while inside
+ // the button's hover target bounds should perform a click action.
+ if (mAccessibilityManager.isEnabled()
+ && mAccessibilityManager.isTouchExplorationEnabled()) {
+ final int left = v.getPaddingLeft();
+ final int right = (v.getWidth() - v.getPaddingRight());
+ final int top = v.getPaddingTop();
+ final int bottom = (v.getHeight() - v.getPaddingBottom());
+
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_HOVER_ENTER:
+ // Lift-to-type temporarily disables double-tap activation.
+ v.setClickable(false);
+ break;
+ case MotionEvent.ACTION_HOVER_EXIT:
+ final int x = (int) event.getX();
+ final int y = (int) event.getY();
+ if ((x > left) && (x < right) && (y > top) && (y < bottom)) {
+ v.performClick();
+ }
+ v.setClickable(true);
+ break;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public void onClick(View v) {
+ // When accessibility is on, simulate press and release to preserve the
+ // semantic meaning of performClick(). Required for Braille support.
+ if (mAccessibilityManager.isEnabled()) {
+ final int id = v.getId();
+ // Checking the press state prevents double activation.
+ if (!v.isPressed() && mDisplayMap.containsKey(id)) {
+ processDtmf(mDisplayMap.get(id), true /* forceShortTone */);
+ }
+ }
+ }
+
+ /**
* Implemented for the TouchListener, process the touch events.
*/
@Override
@@ -764,8 +830,19 @@
* Processes the specified digit as a DTMF key, by playing the
* appropriate DTMF tone, and appending the digit to the EditText
* field that displays the DTMF digits sent so far.
+ *
+ * @see #processDtmf(char, boolean)
*/
private final void processDtmf(char c) {
+ processDtmf(c, false);
+ }
+
+ /**
+ * Processes the specified digit as a DTMF key, by playing the appropriate
+ * DTMF tone (or short tone if requested), and appending the digit to the
+ * EditText field that displays the DTMF digits sent so far.
+ */
+ private final void processDtmf(char c, boolean forceShortTone) {
// if it is a valid key, then update the display and send the dtmf tone.
if (PhoneNumberUtils.is12Key(c)) {
if (DBG) log("updating display and sending dtmf tone for '" + c + "'");
@@ -787,14 +864,14 @@
// Play the tone if it exists.
if (mToneMap.containsKey(c)) {
// begin tone playback.
- startTone(c);
+ startTone(c, forceShortTone);
}
} else if (DBG) {
log("ignoring dtmf request for '" + c + "'");
}
// Any DTMF keypress counts as explicit "user activity".
- PhoneApp.getInstance().pokeUserActivity();
+ PhoneGlobals.getInstance().pokeUserActivity();
}
/**
@@ -858,7 +935,7 @@
/**
* Plays the local tone based the phone type.
*/
- public void startTone(char c) {
+ public void startTone(char c, boolean forceShortTone) {
// Only play the tone if it exists.
if (!mToneMap.containsKey(c)) {
return;
@@ -875,20 +952,28 @@
if (DBG) log("startDtmfTone()...");
// For Short DTMF we need to play the local tone for fixed duration
- if (mShortTone) {
+ if (forceShortTone || mShortTone) {
sendShortDtmfToNetwork(c);
} else {
// Pass as a char to be sent to network
- Log.i(LOG_TAG, "send long dtmf for " + c);
+ if (DBG) log("send long dtmf for " + c);
mCM.startDtmf(c);
}
- startLocalToneIfNeeded(c);
+ startLocalToneIfNeeded(c, forceShortTone);
}
/**
* Plays the local tone based the phone type.
*/
public void startLocalToneIfNeeded(char c) {
+ startLocalToneIfNeeded(c, false);
+ }
+
+ /**
+ * Plays the local tone based the phone type, optionally forcing a short
+ * tone.
+ */
+ private void startLocalToneIfNeeded(char c, boolean forceShortTone) {
// if local tone playback is enabled, start it.
// Only play the tone if it exists.
if (!mToneMap.containsKey(c)) {
@@ -901,7 +986,7 @@
} else {
if (DBG) log("starting local tone " + c);
int toneDuration = -1;
- if (mShortTone) {
+ if (forceShortTone || mShortTone) {
toneDuration = DTMF_DURATION_MS;
}
mToneGenerator.startTone(mToneMap.get(c), toneDuration);
diff --git a/src/com/android/phone/DeleteFdnContactScreen.java b/src/com/android/phone/DeleteFdnContactScreen.java
index 19b10c6..074078c 100644
--- a/src/com/android/phone/DeleteFdnContactScreen.java
+++ b/src/com/android/phone/DeleteFdnContactScreen.java
@@ -36,7 +36,7 @@
* Activity to let the user delete an FDN contact.
*/
public class DeleteFdnContactScreen extends Activity {
- private static final String LOG_TAG = PhoneApp.LOG_TAG;
+ private static final String LOG_TAG = PhoneGlobals.LOG_TAG;
private static final boolean DBG = false;
private static final String INTENT_EXTRA_NAME = "name";
diff --git a/src/com/android/phone/EditFdnContactScreen.java b/src/com/android/phone/EditFdnContactScreen.java
index 36a6b79..cca9a9f 100644
--- a/src/com/android/phone/EditFdnContactScreen.java
+++ b/src/com/android/phone/EditFdnContactScreen.java
@@ -51,7 +51,7 @@
* Activity to let the user add or edit an FDN contact.
*/
public class EditFdnContactScreen extends Activity {
- private static final String LOG_TAG = PhoneApp.LOG_TAG;
+ private static final String LOG_TAG = PhoneGlobals.LOG_TAG;
private static final boolean DBG = false;
// Menu item codes
diff --git a/src/com/android/phone/EditPhoneNumberPreference.java b/src/com/android/phone/EditPhoneNumberPreference.java
index d5ef3a4..86671a8 100644
--- a/src/com/android/phone/EditPhoneNumberPreference.java
+++ b/src/com/android/phone/EditPhoneNumberPreference.java
@@ -260,7 +260,7 @@
}
}
// set the call icon on the title.
- builder.setIcon(R.drawable.ic_launcher_phone);
+ builder.setIcon(R.mipmap.ic_launcher_phone);
}
diff --git a/src/com/android/phone/EmergencyCallHelper.java b/src/com/android/phone/EmergencyCallHelper.java
index 3508b94..7f5b0d2 100644
--- a/src/com/android/phone/EmergencyCallHelper.java
+++ b/src/com/android/phone/EmergencyCallHelper.java
@@ -29,6 +29,7 @@
import android.os.Handler;
import android.os.Message;
import android.os.PowerManager;
+import android.os.UserHandle;
import android.provider.Settings;
import android.telephony.ServiceState;
import android.util.Log;
@@ -65,7 +66,7 @@
private static final int RETRY_TIMEOUT = 4;
private CallController mCallController;
- private PhoneApp mApp;
+ private PhoneGlobals mApp;
private CallManager mCM;
private Phone mPhone;
private String mNumber; // The emergency number we're trying to dial
@@ -77,7 +78,7 @@
public EmergencyCallHelper(CallController callController) {
if (DBG) log("EmergencyCallHelper constructor...");
mCallController = callController;
- mApp = PhoneApp.getInstance();
+ mApp = PhoneGlobals.getInstance();
mCM = mApp.mCM;
}
@@ -346,18 +347,18 @@
// If airplane mode is on, we turn it off the same way that the
// Settings activity turns it off.
- if (Settings.System.getInt(mApp.getContentResolver(),
- Settings.System.AIRPLANE_MODE_ON, 0) > 0) {
+ if (Settings.Global.getInt(mApp.getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_ON, 0) > 0) {
if (DBG) log("==> Turning off airplane mode...");
// Change the system setting
- Settings.System.putInt(mApp.getContentResolver(),
- Settings.System.AIRPLANE_MODE_ON, 0);
+ Settings.Global.putInt(mApp.getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_ON, 0);
// Post the intent
Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
intent.putExtra("state", false);
- mApp.sendBroadcast(intent);
+ mApp.sendBroadcastAsUser(intent, UserHandle.ALL);
} else {
// Otherwise, for some strange reason the radio is off
// (even though the Settings database doesn't think we're
diff --git a/src/com/android/phone/EmergencyCallbackModeExitDialog.java b/src/com/android/phone/EmergencyCallbackModeExitDialog.java
index 2257aba..7758b23 100644
--- a/src/com/android/phone/EmergencyCallbackModeExitDialog.java
+++ b/src/com/android/phone/EmergencyCallbackModeExitDialog.java
@@ -91,7 +91,7 @@
waitForConnectionCompleteThread.start();
// Register ECM timer reset notfication
- mPhone = PhoneApp.getPhone();
+ mPhone = PhoneGlobals.getPhone();
mPhone.registerForEcmTimerReset(mTimerResetHandler, ECM_TIMER_RESET, null);
// Register receiver for intent closing the dialog
diff --git a/src/com/android/phone/EmergencyDialer.java b/src/com/android/phone/EmergencyDialer.java
index a3c8d89..f3d9a14 100644
--- a/src/com/android/phone/EmergencyDialer.java
+++ b/src/com/android/phone/EmergencyDialer.java
@@ -37,8 +37,10 @@
import android.text.method.DialerKeyListener;
import android.util.Log;
import android.view.KeyEvent;
+import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
import android.widget.EditText;
@@ -59,21 +61,28 @@
* Or could we figure out some way to move *this* class into apps/Contacts
* also?
*/
-public class EmergencyDialer extends Activity
- implements View.OnClickListener, View.OnLongClickListener,
- View.OnKeyListener, TextWatcher {
+public class EmergencyDialer extends Activity implements View.OnClickListener,
+ View.OnLongClickListener, View.OnHoverListener, View.OnKeyListener, TextWatcher {
// Keys used with onSaveInstanceState().
private static final String LAST_NUMBER = "lastNumber";
// Intent action for this activity.
public static final String ACTION_DIAL = "com.android.phone.EmergencyDialer.DIAL";
+ // List of dialer button IDs.
+ private static final int[] DIALER_KEYS = new int[] {
+ R.id.one, R.id.two, R.id.three,
+ R.id.four, R.id.five, R.id.six,
+ R.id.seven, R.id.eight, R.id.nine,
+ R.id.star, R.id.zero, R.id.pound };
+
// Debug constants.
private static final boolean DBG = false;
private static final String LOG_TAG = "EmergencyDialer";
- private PhoneApp mApp;
+ private PhoneGlobals mApp;
private StatusBarManager mStatusBarManager;
+ private AccessibilityManager mAccessibilityManager;
/** The length of DTMF tones in milliseconds */
private static final int TONE_LENGTH_MS = 150;
@@ -86,6 +95,8 @@
private static final int BAD_EMERGENCY_NUMBER_DIALOG = 0;
+ private static final int USER_ACTIVITY_TIMEOUT_WHEN_NO_PROX_SENSOR = 15000; // millis
+
EditText mDigits;
private View mDialButton;
private View mDelete;
@@ -144,11 +155,18 @@
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
- mApp = PhoneApp.getInstance();
+ mApp = PhoneGlobals.getInstance();
mStatusBarManager = (StatusBarManager) getSystemService(Context.STATUS_BAR_SERVICE);
+ mAccessibilityManager = (AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE);
// Allow this activity to be displayed in front of the keyguard / lockscreen.
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+ WindowManager.LayoutParams lp = getWindow().getAttributes();
+ lp.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
+ if (!mApp.proximitySensorModeEnabled()) {
+ // When no proximity sensor is available, use a shorter timeout.
+ lp.userActivityTimeout = USER_ACTIVITY_TIMEOUT_WHEN_NO_PROX_SENSOR;
+ }
+ getWindow().setAttributes(lp);
setContentView(R.layout.emergency_dialer);
@@ -157,6 +175,10 @@
mDigits.setOnClickListener(this);
mDigits.setOnKeyListener(this);
mDigits.setLongClickable(false);
+ if (mAccessibilityManager.isEnabled()) {
+ // The text view must be selected to send accessibility events.
+ mDigits.setSelected(true);
+ }
maybeAddNumberFormatting();
// Check for the presence of the keypad
@@ -260,22 +282,14 @@
private void setupKeypad() {
// Setup the listeners for the buttons
- findViewById(R.id.one).setOnClickListener(this);
- findViewById(R.id.two).setOnClickListener(this);
- findViewById(R.id.three).setOnClickListener(this);
- findViewById(R.id.four).setOnClickListener(this);
- findViewById(R.id.five).setOnClickListener(this);
- findViewById(R.id.six).setOnClickListener(this);
- findViewById(R.id.seven).setOnClickListener(this);
- findViewById(R.id.eight).setOnClickListener(this);
- findViewById(R.id.nine).setOnClickListener(this);
- findViewById(R.id.star).setOnClickListener(this);
+ for (int id : DIALER_KEYS) {
+ final View key = findViewById(id);
+ key.setOnClickListener(this);
+ key.setOnHoverListener(this);
+ }
View view = findViewById(R.id.zero);
- view.setOnClickListener(this);
view.setOnLongClickListener(this);
-
- findViewById(R.id.pound).setOnClickListener(this);
}
/**
@@ -405,6 +419,40 @@
}
/**
+ * Implemented for {@link android.view.View.OnHoverListener}. Handles touch
+ * events for accessibility when touch exploration is enabled.
+ */
+ @Override
+ public boolean onHover(View v, MotionEvent event) {
+ // When touch exploration is turned on, lifting a finger while inside
+ // the button's hover target bounds should perform a click action.
+ if (mAccessibilityManager.isEnabled()
+ && mAccessibilityManager.isTouchExplorationEnabled()) {
+
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_HOVER_ENTER:
+ // Lift-to-type temporarily disables double-tap activation.
+ v.setClickable(false);
+ break;
+ case MotionEvent.ACTION_HOVER_EXIT:
+ final int left = v.getPaddingLeft();
+ final int right = (v.getWidth() - v.getPaddingRight());
+ final int top = v.getPaddingTop();
+ final int bottom = (v.getHeight() - v.getPaddingBottom());
+ final int x = (int) event.getX();
+ final int y = (int) event.getY();
+ if ((x > left) && (x < right) && (y > top) && (y < bottom)) {
+ v.performClick();
+ }
+ v.setClickable(true);
+ break;
+ }
+ }
+
+ return false;
+ }
+
+ /**
* called for long touch events
*/
@Override
@@ -456,7 +504,6 @@
// There is no need to do anything with the wake lock.
if (DBG) Log.d(LOG_TAG, "disabling status bar, set to long timeout");
mStatusBarManager.disable(StatusBarManager.DISABLE_EXPAND);
- mApp.setScreenTimeout(PhoneApp.ScreenTimeoutDuration.MEDIUM);
updateDialAndDeleteButtonStateEnabledAttr();
}
@@ -467,7 +514,6 @@
// There is no need to do anything with the wake lock.
if (DBG) Log.d(LOG_TAG, "reenabling status bar and closing the dialer");
mStatusBarManager.disable(StatusBarManager.DISABLE_NONE);
- mApp.setScreenTimeout(PhoneApp.ScreenTimeoutDuration.DEFAULT);
super.onPause();
diff --git a/src/com/android/phone/EnableFdnScreen.java b/src/com/android/phone/EnableFdnScreen.java
index 305407f..0db47c3 100644
--- a/src/com/android/phone/EnableFdnScreen.java
+++ b/src/com/android/phone/EnableFdnScreen.java
@@ -36,7 +36,7 @@
* UI to enable/disable FDN.
*/
public class EnableFdnScreen extends Activity {
- private static final String LOG_TAG = PhoneApp.LOG_TAG;
+ private static final String LOG_TAG = PhoneGlobals.LOG_TAG;
private static final boolean DBG = false;
private static final int ENABLE_FDN_COMPLETE = 100;
@@ -67,7 +67,7 @@
setContentView(R.layout.enable_fdn_screen);
setupView();
- mPhone = PhoneApp.getPhone();
+ mPhone = PhoneGlobals.getPhone();
mEnable = !mPhone.getIccCard().getIccFdnEnabled();
int id = mEnable ? R.string.enable_fdn : R.string.disable_fdn;
@@ -77,7 +77,7 @@
@Override
protected void onResume() {
super.onResume();
- mPhone = PhoneApp.getPhone();
+ mPhone = PhoneGlobals.getPhone();
}
private void setupView() {
diff --git a/src/com/android/phone/EnableIccPinScreen.java b/src/com/android/phone/EnableIccPinScreen.java
index a623d46..160978f 100644
--- a/src/com/android/phone/EnableIccPinScreen.java
+++ b/src/com/android/phone/EnableIccPinScreen.java
@@ -36,7 +36,7 @@
* UI to enable/disable the ICC PIN.
*/
public class EnableIccPinScreen extends Activity {
- private static final String LOG_TAG = PhoneApp.LOG_TAG;
+ private static final String LOG_TAG = PhoneGlobals.LOG_TAG;
private static final int ENABLE_ICC_PIN_COMPLETE = 100;
private static final boolean DBG = false;
@@ -67,7 +67,7 @@
setContentView(R.layout.enable_sim_pin_screen);
setupView();
- mPhone = PhoneApp.getPhone();
+ mPhone = PhoneGlobals.getPhone();
mEnable = !mPhone.getIccCard().getIccLockEnabled();
int id = mEnable ? R.string.enable_sim_pin : R.string.disable_sim_pin;
diff --git a/src/com/android/phone/FakePhoneActivity.java b/src/com/android/phone/FakePhoneActivity.java
index 07fce9c..686a766 100644
--- a/src/com/android/phone/FakePhoneActivity.java
+++ b/src/com/android/phone/FakePhoneActivity.java
@@ -54,10 +54,10 @@
}
});
- mRadioControl = PhoneApp.getPhone().getSimulatedRadioControl();
+ mRadioControl = PhoneGlobals.getPhone().getSimulatedRadioControl();
- Log.i(TAG, "- PhoneApp.getInstance(): " + PhoneApp.getInstance());
- Log.i(TAG, "- PhoneApp.getPhone(): " + PhoneApp.getPhone());
+ Log.i(TAG, "- PhoneApp.getInstance(): " + PhoneGlobals.getInstance());
+ Log.i(TAG, "- PhoneApp.getPhone(): " + PhoneGlobals.getPhone());
Log.i(TAG, "- mRadioControl: " + mRadioControl);
}
diff --git a/src/com/android/phone/FdnSetting.java b/src/com/android/phone/FdnSetting.java
index 4f21e2f..283d612 100644
--- a/src/com/android/phone/FdnSetting.java
+++ b/src/com/android/phone/FdnSetting.java
@@ -40,7 +40,7 @@
public class FdnSetting extends PreferenceActivity
implements EditPinPreference.OnPinEnteredListener, DialogInterface.OnCancelListener {
- private static final String LOG_TAG = PhoneApp.LOG_TAG;
+ private static final String LOG_TAG = PhoneGlobals.LOG_TAG;
private static final boolean DBG = false;
private Phone mPhone;
@@ -423,7 +423,7 @@
addPreferencesFromResource(R.xml.fdn_setting);
- mPhone = PhoneApp.getPhone();
+ mPhone = PhoneGlobals.getPhone();
//get UI object references
PreferenceScreen prefSet = getPreferenceScreen();
@@ -458,7 +458,7 @@
@Override
protected void onResume() {
super.onResume();
- mPhone = PhoneApp.getPhone();
+ mPhone = PhoneGlobals.getPhone();
updateEnableFDN();
}
diff --git a/src/com/android/phone/GetPin2Screen.java b/src/com/android/phone/GetPin2Screen.java
index c3fda66..a06b0cf 100644
--- a/src/com/android/phone/GetPin2Screen.java
+++ b/src/com/android/phone/GetPin2Screen.java
@@ -34,7 +34,7 @@
* Pin2 entry screen.
*/
public class GetPin2Screen extends Activity implements TextView.OnEditorActionListener {
- private static final String LOG_TAG = PhoneApp.LOG_TAG;
+ private static final String LOG_TAG = PhoneGlobals.LOG_TAG;
private EditText mPin2Field;
private Button mOkButton;
diff --git a/src/com/android/phone/GsmUmtsAdditionalCallOptions.java b/src/com/android/phone/GsmUmtsAdditionalCallOptions.java
index 131ffe5..cd400f9 100644
--- a/src/com/android/phone/GsmUmtsAdditionalCallOptions.java
+++ b/src/com/android/phone/GsmUmtsAdditionalCallOptions.java
@@ -13,7 +13,7 @@
public class GsmUmtsAdditionalCallOptions extends
TimeConsumingPreferenceActivity {
private static final String LOG_TAG = "GsmUmtsAdditionalCallOptions";
- private final boolean DBG = (PhoneApp.DBG_LEVEL >= 2);
+ private final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
private static final String BUTTON_CLIR_KEY = "button_clir_key";
private static final String BUTTON_CW_KEY = "button_cw_key";
diff --git a/src/com/android/phone/GsmUmtsCallForwardOptions.java b/src/com/android/phone/GsmUmtsCallForwardOptions.java
index 09459eb..5f68135 100644
--- a/src/com/android/phone/GsmUmtsCallForwardOptions.java
+++ b/src/com/android/phone/GsmUmtsCallForwardOptions.java
@@ -18,7 +18,7 @@
public class GsmUmtsCallForwardOptions extends TimeConsumingPreferenceActivity {
private static final String LOG_TAG = "GsmUmtsCallForwardOptions";
- private final boolean DBG = (PhoneApp.DBG_LEVEL >= 2);
+ private final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
private static final String NUM_PROJECTION[] = {Phone.NUMBER};
diff --git a/src/com/android/phone/GsmUmtsCallOptions.java b/src/com/android/phone/GsmUmtsCallOptions.java
index ed28be9..a9a1940 100644
--- a/src/com/android/phone/GsmUmtsCallOptions.java
+++ b/src/com/android/phone/GsmUmtsCallOptions.java
@@ -27,7 +27,7 @@
public class GsmUmtsCallOptions extends PreferenceActivity {
private static final String LOG_TAG = "GsmUmtsCallOptions";
- private final boolean DBG = (PhoneApp.DBG_LEVEL >= 2);
+ private final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
@Override
protected void onCreate(Bundle icicle) {
@@ -35,7 +35,7 @@
addPreferencesFromResource(R.xml.gsm_umts_call_options);
- if (PhoneApp.getPhone().getPhoneType() != PhoneConstants.PHONE_TYPE_GSM) {
+ if (PhoneGlobals.getPhone().getPhoneType() != PhoneConstants.PHONE_TYPE_GSM) {
//disable the entire screen
getPreferenceScreen().setEnabled(false);
}
diff --git a/src/com/android/phone/IccNetworkDepersonalizationPanel.java b/src/com/android/phone/IccNetworkDepersonalizationPanel.java
index 3688be2..aa582a1 100644
--- a/src/com/android/phone/IccNetworkDepersonalizationPanel.java
+++ b/src/com/android/phone/IccNetworkDepersonalizationPanel.java
@@ -39,7 +39,7 @@
/**
* "SIM network unlock" PIN entry screen.
*
- * @see PhoneApp.EVENT_SIM_NETWORK_LOCKED
+ * @see PhoneGlobals.EVENT_SIM_NETWORK_LOCKED
*
* TODO: This UI should be part of the lock screen, not the
* phone app (see bug 1804111).
@@ -148,7 +148,7 @@
mStatusPanel = (LinearLayout) findViewById(R.id.status_panel);
mStatusText = (TextView) findViewById(R.id.status_text);
- mPhone = PhoneApp.getPhone();
+ mPhone = PhoneGlobals.getPhone();
}
@Override
diff --git a/src/com/android/phone/IccPanel.java b/src/com/android/phone/IccPanel.java
index c80b5dc..e603a06 100644
--- a/src/com/android/phone/IccPanel.java
+++ b/src/com/android/phone/IccPanel.java
@@ -29,7 +29,7 @@
* Base class for ICC-related panels in the Phone UI.
*/
public class IccPanel extends Dialog {
- protected static final String TAG = PhoneApp.LOG_TAG;
+ protected static final String TAG = PhoneGlobals.LOG_TAG;
private StatusBarManager mStatusBarManager;
@@ -65,7 +65,7 @@
// TODO: we shouldn't need the mStatusBarManager calls here either,
// once this dialog gets moved into the framework and becomes a truly
// full-screen UI.
- PhoneApp app = PhoneApp.getInstance();
+ PhoneGlobals app = PhoneGlobals.getInstance();
mStatusBarManager = (StatusBarManager) app.getSystemService(Context.STATUS_BAR_SERVICE);
requestWindowFeature(Window.FEATURE_NO_TITLE);
diff --git a/src/com/android/phone/InCallControlState.java b/src/com/android/phone/InCallControlState.java
index e901cf1..e5c7f20 100644
--- a/src/com/android/phone/InCallControlState.java
+++ b/src/com/android/phone/InCallControlState.java
@@ -42,7 +42,7 @@
*/
public class InCallControlState {
private static final String LOG_TAG = "InCallControlState";
- private static final boolean DBG = (PhoneApp.DBG_LEVEL >= 2);
+ private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
private InCallScreen mInCallScreen;
private CallManager mCM;
diff --git a/src/com/android/phone/InCallScreen.java b/src/com/android/phone/InCallScreen.java
index 05675e6..f8ba9c7 100755
--- a/src/com/android/phone/InCallScreen.java
+++ b/src/com/android/phone/InCallScreen.java
@@ -25,6 +25,7 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.IBluetoothHeadsetPhone;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -41,6 +42,7 @@
import android.os.Handler;
import android.os.Message;
import android.os.PowerManager;
+import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.telephony.ServiceState;
@@ -83,8 +85,8 @@
private static final String LOG_TAG = "InCallScreen";
private static final boolean DBG =
- (PhoneApp.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
- private static final boolean VDBG = (PhoneApp.DBG_LEVEL >= 2);
+ (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
+ private static final boolean VDBG = (PhoneGlobals.DBG_LEVEL >= 2);
/**
* Intent extra used to specify whether the DTMF dialpad should be
@@ -151,7 +153,6 @@
private static final int DONT_ADD_VOICEMAIL_NUMBER = 107;
private static final int DELAYED_CLEANUP_AFTER_DISCONNECT = 108;
private static final int SUPP_SERVICE_FAILED = 110;
- private static final int ALLOW_SCREEN_ON = 112;
private static final int REQUEST_UPDATE_BLUETOOTH_INDICATION = 114;
private static final int PHONE_CDMA_CALL_WAITING = 115;
private static final int REQUEST_CLOSE_SPC_ERROR_NOTICE = 118;
@@ -187,7 +188,7 @@
private boolean mRegisteredForPhoneStates;
- private PhoneApp mApp;
+ private PhoneGlobals mApp;
private CallManager mCM;
// TODO: need to clean up all remaining uses of mPhone.
@@ -197,9 +198,8 @@
// based on the current foreground Call.)
private Phone mPhone;
- private BluetoothHandsfree mBluetoothHandsfree;
private BluetoothHeadset mBluetoothHeadset;
- private BluetoothAdapter mAdapter;
+ private BluetoothAdapter mBluetoothAdapter;
private boolean mBluetoothConnectionPending;
private long mBluetoothConnectionRequestTime;
@@ -316,7 +316,7 @@
// onMMIInitiate((AsyncResult) msg.obj);
// break;
- case PhoneApp.MMI_CANCEL:
+ case PhoneGlobals.MMI_CANCEL:
onMMICancel();
break;
@@ -324,7 +324,7 @@
// since the message display class has been replaced with
// a system dialog in PhoneUtils.displayMMIComplete(), we
// should finish the activity here to close the window.
- case PhoneApp.MMI_COMPLETE:
+ case PhoneGlobals.MMI_COMPLETE:
onMMIComplete((MmiCode) ((AsyncResult) msg.obj).result);
break;
@@ -344,15 +344,6 @@
delayedCleanupAfterDisconnect();
break;
- case ALLOW_SCREEN_ON:
- if (VDBG) log("ALLOW_SCREEN_ON message...");
- // Undo our previous call to preventScreenOn(true).
- // (Note this will cause the screen to turn on
- // immediately, if it's currently off because of a
- // prior preventScreenOn(true) call.)
- mApp.preventScreenOn(false);
- break;
-
case REQUEST_UPDATE_BLUETOOTH_INDICATION:
if (VDBG) log("REQUEST_UPDATE_BLUETOOTH_INDICATION...");
// The bluetooth headset state changed, so some UI
@@ -448,7 +439,7 @@
super.onCreate(icicle);
// Make sure this is a voice-capable device.
- if (!PhoneApp.sVoiceCapable) {
+ if (!PhoneGlobals.sVoiceCapable) {
// There should be no way to ever reach the InCallScreen on a
// non-voice-capable device, since this activity is not exported by
// our manifest, and we explicitly disable any other external APIs
@@ -459,11 +450,12 @@
return;
}
- mApp = PhoneApp.getInstance();
+ mApp = PhoneGlobals.getInstance();
mApp.setInCallScreenInstance(this);
// set this flag so this activity will stay in front of the keyguard
- int flags = WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
+ int flags = WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
if (mApp.getPhoneState() == PhoneConstants.State.OFFHOOK) {
// While we are in call, the in-call screen should dismiss the keyguard.
// This allows the user to press Home to go directly home without going through
@@ -472,24 +464,26 @@
// bypass the keyguard if the call is not answered or declined.
flags |= WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
}
- getWindow().addFlags(flags);
+
+ WindowManager.LayoutParams lp = getWindow().getAttributes();
+ lp.flags |= flags;
+ if (!mApp.proximitySensorModeEnabled()) {
+ // If we don't have a proximity sensor, then the in-call screen explicitly
+ // controls user activity. This is to prevent spurious touches from waking
+ // the display.
+ lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY;
+ }
+ getWindow().setAttributes(lp);
setPhone(mApp.phone); // Sets mPhone
mCM = mApp.mCM;
log("- onCreate: phone state = " + mCM.getState());
- mBluetoothHandsfree = mApp.getBluetoothHandsfree();
- if (VDBG) log("- mBluetoothHandsfree: " + mBluetoothHandsfree);
-
- if (mBluetoothHandsfree != null) {
- // The PhoneApp only creates a BluetoothHandsfree instance in the
- // first place if BluetoothAdapter.getDefaultAdapter()
- // succeeds. So at this point we know the device is BT-capable.
- mAdapter = BluetoothAdapter.getDefaultAdapter();
- mAdapter.getProfileProxy(getApplicationContext(), mBluetoothProfileServiceListener,
+ mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ if (mBluetoothAdapter != null) {
+ mBluetoothAdapter.getProfileProxy(getApplicationContext(), mBluetoothProfileServiceListener,
BluetoothProfile.HEADSET);
-
}
requestWindowFeature(Window.FEATURE_NO_TITLE);
@@ -497,6 +491,13 @@
// Inflate everything in incall_screen.xml and add it to the screen.
setContentView(R.layout.incall_screen);
+ // If in landscape, then one of the ViewStubs (instead of <include>) is used for the
+ // incall_touch_ui, because CDMA and GSM button layouts are noticeably different.
+ final ViewStub touchUiStub = (ViewStub) findViewById(
+ mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA
+ ? R.id.inCallTouchUiCdmaStub : R.id.inCallTouchUiStub);
+ if (touchUiStub != null) touchUiStub.inflate();
+
initInCallScreen();
registerForPhoneStates();
@@ -560,11 +561,6 @@
final InCallUiState inCallUiState = mApp.inCallUiState;
if (VDBG) inCallUiState.dumpState();
- // Touch events are never considered "user activity" while the
- // InCallScreen is active, so that unintentional touches won't
- // prevent the device from going to sleep.
- mApp.setIgnoreTouchUserActivity(true);
-
updateExpandedViewState();
// ...and update the in-call notification too, since the status bar
@@ -711,40 +707,6 @@
// InCallScreen is now active.
EventLog.writeEvent(EventLogTags.PHONE_UI_ENTER);
- // Coming to the foreground while in an incoming call is ringing.
- // We need to do something special.
- if (mCM.getState() == PhoneConstants.State.RINGING) {
- // If the phone is ringing, we *should* already be holding a
- // full wake lock (which we would have acquired before
- // firing off the intent that brought us here; see
- // CallNotifier.showIncomingCall().)
- //
- // We also called preventScreenOn(true) at that point, to
- // avoid cosmetic glitches while we were being launched.
- // So now we need to post an ALLOW_SCREEN_ON message to
- // (eventually) undo the prior preventScreenOn(true) call.
- //
- // (In principle we shouldn't do this until after our first
- // layout/draw pass. But in practice, the delay caused by
- // simply waiting for the end of the message queue is long
- // enough to avoid any flickering of the lock screen before
- // the InCallScreen comes up.)
- if (VDBG) log("- posting ALLOW_SCREEN_ON message...");
- mHandler.removeMessages(ALLOW_SCREEN_ON);
- mHandler.sendEmptyMessage(ALLOW_SCREEN_ON);
-
- // TODO: There ought to be a more elegant way of doing this,
- // probably by having the PowerManager and ActivityManager
- // work together to let apps request that the screen on/off
- // state be synchronized with the Activity lifecycle.
- // (See bug 1648751.)
- } else {
- // The phone isn't ringing; this is either an outgoing call, or
- // we're returning to a call in progress. There *shouldn't* be
- // any prior preventScreenOn(true) call that we need to undo,
- // but let's do this just to be safe:
- mApp.preventScreenOn(false);
- }
// Update the poke lock and wake lock when we move to the foreground.
// This will be no-op when prox sensor is effective.
mApp.updateWakeState();
@@ -779,7 +741,7 @@
if (!mPhone.getPendingMmiCodes().isEmpty()) {
if (mMmiStartedDialog == null) {
MmiCode mmiCode = mPhone.getPendingMmiCodes().get(0);
- Message message = Message.obtain(mHandler, PhoneApp.MMI_CANCEL);
+ Message message = Message.obtain(mHandler, PhoneGlobals.MMI_CANCEL);
mMmiStartedDialog = PhoneUtils.displayMMIInitiate(this, mmiCode,
message, mMmiStartedDialog);
// mInCallScreen needs to receive MMI_COMPLETE/MMI_CANCEL event from telephony,
@@ -894,17 +856,6 @@
// foreground.)
unregisterReceiver(mReceiver);
- // Re-enable "user activity" for touch events.
- // We actually do this slightly *after* onPause(), to work around a
- // race condition where a touch can come in after we've paused
- // but before the device actually goes to sleep.
- // TODO: The PowerManager itself should prevent this from happening.
- mHandler.postDelayed(new Runnable() {
- public void run() {
- mApp.setIgnoreTouchUserActivity(false);
- }
- }, 500);
-
// Make sure we revert the poke lock and wake lock when we move to
// the background.
mApp.updateWakeState();
@@ -989,7 +940,7 @@
// are moving out of the foreground.
if (mBluetoothHeadset != null) {
- mAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mBluetoothHeadset);
+ mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mBluetoothHeadset);
mBluetoothHeadset = null;
}
@@ -1099,7 +1050,7 @@
// message display class in PhoneUtils.displayMMIComplete().
// We'll listen for that message too, so that we can finish
// the activity at the same time.
- mCM.registerForMmiComplete(mHandler, PhoneApp.MMI_COMPLETE, null);
+ mCM.registerForMmiComplete(mHandler, PhoneGlobals.MMI_COMPLETE, null);
mCM.registerForCallWaiting(mHandler, PHONE_CDMA_CALL_WAITING, null);
mCM.registerForPostDialCharacter(mHandler, POST_ON_DIAL_CHARS, null);
mCM.registerForSuppServiceFailed(mHandler, SUPP_SERVICE_FAILED, null);
@@ -1721,8 +1672,8 @@
// Get the Auto-retry setting only if Phone State is IDLE,
// else let it stay as AUTO_RETRY_OFF
if (currentlyIdle) {
- autoretrySetting = android.provider.Settings.System.getInt(mPhone.getContext().
- getContentResolver(), android.provider.Settings.System.CALL_AUTO_RETRY, 0);
+ autoretrySetting = android.provider.Settings.Global.getInt(mPhone.getContext().
+ getContentResolver(), android.provider.Settings.Global.CALL_AUTO_RETRY, 0);
}
}
@@ -2685,7 +2636,7 @@
if ((mLastDisconnectCause != Connection.DisconnectCause.INCOMING_MISSED)
&& (mLastDisconnectCause != Connection.DisconnectCause.INCOMING_REJECTED)
&& !isPhoneStateRestricted()
- && PhoneApp.sVoiceCapable) {
+ && PhoneGlobals.sVoiceCapable) {
final Intent intent = mApp.createPhoneEndIntentUsingCallOrigin();
ActivityOptions opts = ActivityOptions.makeCustomAnimation(this,
R.anim.activity_close_enter, R.anim.activity_close_exit);
@@ -2711,8 +2662,8 @@
// stay in the activity history.
}
- endInCallScreenSession();
}
+ endInCallScreenSession();
// Reset the call origin when the session ends and this in-call UI is being finished.
mApp.setLatestActiveCallOrigin(null);
@@ -3700,13 +3651,17 @@
// multiple background calls, for now, always act on the first background calll.
PhoneUtils.switchHoldingAndActive(mCM.getFirstActiveBgCall());
- // If we have a valid BluetoothHandsfree then since CDMA network or
+ // If we have a valid BluetoothPhoneService then since CDMA network or
// Telephony FW does not send us information on which caller got swapped
- // we need to update the second call active state in BluetoothHandsfree internally
+ // we need to update the second call active state in BluetoothPhoneService internally
if (mCM.getBgPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
- BluetoothHandsfree bthf = mApp.getBluetoothHandsfree();
- if (bthf != null) {
- bthf.cdmaSwapSecondCallState();
+ IBluetoothHeadsetPhone btPhone = mApp.getBluetoothPhoneService();
+ if (btPhone != null) {
+ try {
+ btPhone.cdmaSwapSecondCallState();
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, Log.getStackTraceString(new Throwable()));
+ }
}
}
@@ -4117,13 +4072,6 @@
// headset (which we don't do from the Phone UI!) but also lets you
// get the address of the currently active headset and see whether
// it's currently connected.
- //
- // - BluetoothHandsfree is the API to control the audio connection to
- // a bluetooth headset. We use this API to switch the headset on and
- // off when the user presses the "Bluetooth" button.
- // Our BluetoothHandsfree instance (mBluetoothHandsfree) is created
- // by the PhoneApp and will be null if the device is not BT capable.
- //
/**
* @return true if the Bluetooth on/off switch in the UI should be
@@ -4132,11 +4080,6 @@
*/
/* package */ boolean isBluetoothAvailable() {
if (VDBG) log("isBluetoothAvailable()...");
- if (mBluetoothHandsfree == null) {
- // Device is not BT capable.
- if (VDBG) log(" ==> FALSE (not BT capable)");
- return false;
- }
// There's no need to ask the Bluetooth system service if BT is enabled:
//
@@ -4173,14 +4116,20 @@
}
/**
- * @return true if a BT device is available, and its audio is currently connected.
+ * @return true if a BT Headset is available, and its audio is currently connected.
*/
/* package */ boolean isBluetoothAudioConnected() {
- if (mBluetoothHandsfree == null) {
- if (VDBG) log("isBluetoothAudioConnected: ==> FALSE (null mBluetoothHandsfree)");
+ if (mBluetoothHeadset == null) {
+ if (VDBG) log("isBluetoothAudioConnected: ==> FALSE (null mBluetoothHeadset)");
return false;
}
- boolean isAudioOn = mBluetoothHandsfree.isAudioOn();
+ List<BluetoothDevice> deviceList = mBluetoothHeadset.getConnectedDevices();
+
+ if (deviceList.isEmpty()) {
+ return false;
+ }
+ BluetoothDevice device = deviceList.get(0);
+ boolean isAudioOn = mBluetoothHeadset.isAudioConnected(device);
if (VDBG) log("isBluetoothAudioConnected: ==> isAudioOn = " + isAudioOn);
return isAudioOn;
}
@@ -4190,7 +4139,7 @@
* see InCallControlState.bluetoothIndicatorOn.
*
* @return true if a BT device is available and its audio is currently connected,
- * <b>or</b> if we issued a BluetoothHandsfree.userWantsAudioOn()
+ * <b>or</b> if we issued a BluetoothHeadset.connectAudio()
* call within the last 5 seconds (which presumably means
* that the BT audio connection is currently being set
* up, and will be connected soon.)
@@ -4201,7 +4150,7 @@
return true;
}
- // If we issued a userWantsAudioOn() call "recently enough", even
+ // If we issued a connectAudio() call "recently enough", even
// if BT isn't actually connected yet, let's still pretend BT is
// on. This makes the onscreen indication more responsive.
if (mBluetoothConnectionPending) {
@@ -4244,8 +4193,7 @@
log("= PhoneApp.showBluetoothIndication: "
+ mApp.showBluetoothIndication());
log("=");
- if (mBluetoothHandsfree != null) {
- log("= BluetoothHandsfree.isAudioOn: " + mBluetoothHandsfree.isAudioOn());
+ if (mBluetoothAdapter != null) {
if (mBluetoothHeadset != null) {
List<BluetoothDevice> deviceList = mBluetoothHeadset.getConnectedDevices();
@@ -4254,23 +4202,26 @@
log("= BluetoothHeadset.getCurrentDevice: " + device);
log("= BluetoothHeadset.State: "
+ mBluetoothHeadset.getConnectionState(device));
+ log("= BluetoothHeadset audio connected: " +
+ mBluetoothHeadset.isAudioConnected(device));
}
} else {
log("= mBluetoothHeadset is null");
}
} else {
- log("= mBluetoothHandsfree is null; device is not BT capable");
+ log("= mBluetoothAdapter is null; device is not BT capable");
}
}
/* package */ void connectBluetoothAudio() {
if (VDBG) log("connectBluetoothAudio()...");
- if (mBluetoothHandsfree != null) {
- mBluetoothHandsfree.userWantsAudioOn();
+ if (mBluetoothHeadset != null) {
+ // TODO(BT) check return
+ mBluetoothHeadset.connectAudio();
}
// Watch out: The bluetooth connection doesn't happen instantly;
- // the userWantsAudioOn() call returns instantly but does its real
+ // the connectAudio() call returns instantly but does its real
// work in another thread. The mBluetoothConnectionPending flag
// is just a little trickery to ensure that the onscreen UI updates
// instantly. (See isBluetoothAudioConnectedOrPending() above.)
@@ -4280,8 +4231,8 @@
/* package */ void disconnectBluetoothAudio() {
if (VDBG) log("disconnectBluetoothAudio()...");
- if (mBluetoothHandsfree != null) {
- mBluetoothHandsfree.userWantsAudioOff();
+ if (mBluetoothHeadset != null) {
+ mBluetoothHeadset.disconnectAudio();
}
mBluetoothConnectionPending = false;
}
@@ -4535,11 +4486,12 @@
/**
* Manually handle configuration changes.
*
- * We specify android:configChanges="orientation|keyboardHidden|uiMode" in
- * our manifest to make sure the system doesn't destroy and re-create us
- * due to the above config changes. Instead, this method will be called,
- * and should manually rebuild the onscreen UI to keep it in sync with the
- * current configuration.
+ * Originally android:configChanges was set to "orientation|keyboardHidden|uiMode"
+ * in order "to make sure the system doesn't destroy and re-create us due to the
+ * above config changes". However it is currently set to "keyboardHidden" since
+ * the system needs to handle rotation when inserted into a compatible cardock.
+ * Even without explicitly handling orientation and uiMode, the app still runs
+ * and does not drop the call when rotated.
*
*/
public void onConfigurationChanged(Configuration newConfig) {
@@ -4652,4 +4604,11 @@
log("Requested to remove provider info after " + PROVIDER_INFO_TIMEOUT + " msec.");
}
}
+
+ /**
+ * Indicates whether or not the QuickResponseDialog is currently showing in the call screen
+ */
+ public boolean isQuickResponseDialogShowing() {
+ return mRespondViaSmsManager != null && mRespondViaSmsManager.isShowingPopup();
+ }
}
diff --git a/src/com/android/phone/InCallScreenShowActivation.java b/src/com/android/phone/InCallScreenShowActivation.java
index ad13186..221b915 100644
--- a/src/com/android/phone/InCallScreenShowActivation.java
+++ b/src/com/android/phone/InCallScreenShowActivation.java
@@ -42,7 +42,7 @@
public class InCallScreenShowActivation extends Activity {
private static final String LOG_TAG = "InCallScreenShowActivation";
private static final boolean DBG =
- (PhoneApp.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
+ (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
@Override
protected void onCreate(Bundle icicle) {
@@ -56,7 +56,7 @@
Log.d(LOG_TAG, " - extras = " + extras);
}
- PhoneApp app = PhoneApp.getInstance();
+ PhoneGlobals app = PhoneGlobals.getInstance();
Phone phone = app.getPhone();
if (!TelephonyCapabilities.supportsOtasp(phone)) {
Log.w(LOG_TAG, "CDMA Provisioning not supported on this device");
@@ -69,7 +69,7 @@
// On voice-capable devices, we perform CDMA provisioning in
// "interactive" mode by directly launching the InCallScreen.
- boolean interactiveMode = PhoneApp.sVoiceCapable;
+ boolean interactiveMode = PhoneGlobals.sVoiceCapable;
Log.d(LOG_TAG, "ACTION_PERFORM_CDMA_PROVISIONING (interactiveMode = "
+ interactiveMode + ")...");
diff --git a/src/com/android/phone/InCallTouchUi.java b/src/com/android/phone/InCallTouchUi.java
index 84176d1..986765e 100644
--- a/src/com/android/phone/InCallTouchUi.java
+++ b/src/com/android/phone/InCallTouchUi.java
@@ -41,6 +41,7 @@
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.PopupMenu;
+import android.widget.TextView;
import android.widget.Toast;
import com.android.internal.telephony.Call;
@@ -61,7 +62,7 @@
implements View.OnClickListener, View.OnLongClickListener, OnTriggerListener,
PopupMenu.OnMenuItemClickListener, PopupMenu.OnDismissListener {
private static final String LOG_TAG = "InCallTouchUi";
- private static final boolean DBG = (PhoneApp.DBG_LEVEL >= 2);
+ private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
// Incoming call widget targets
private static final int ANSWER_CALL_ID = 0; // drag right
@@ -76,7 +77,7 @@
private InCallScreen mInCallScreen;
// Phone app instance
- private PhoneApp mApp;
+ private PhoneGlobals mApp;
// UI containers / elements
private GlowPadView mIncomingCallWidget; // UI used for an incoming call
@@ -142,7 +143,7 @@
if (DBG) log("InCallTouchUi constructor...");
if (DBG) log("- this = " + this);
if (DBG) log("- context " + context + ", attrs " + attrs);
- mApp = PhoneApp.getInstance();
+ mApp = PhoneGlobals.getInstance();
}
void setInCallScreenInstance(InCallScreen inCallScreen) {
@@ -207,14 +208,20 @@
// Buttons shown on the "extra button row", only visible in certain (rare) states.
mExtraButtonRow = (ViewStub) mInCallControls.findViewById(R.id.extraButtonRow);
- // Add a custom OnTouchListener to manually shrink the "hit target".
- View.OnTouchListener smallerHitTargetTouchListener = new SmallerHitTargetTouchListener();
- mEndButton.setOnTouchListener(smallerHitTargetTouchListener);
+ // If in PORTRAIT, add a custom OnTouchListener to shrink the "hit target".
+ if (!PhoneUtils.isLandscape(this.getContext())) {
+ mEndButton.setOnTouchListener(new SmallerHitTargetTouchListener());
+ }
+
}
/**
* Updates the visibility and/or state of our UI elements, based on
* the current state of the phone.
+ *
+ * TODO: This function should be relying on a state defined by InCallScreen,
+ * and not generic call states. The incoming call screen handles more states
+ * than Call.State or PhoneConstant.State know about.
*/
/* package */ void updateState(CallManager cm) {
if (mInCallScreen == null) {
@@ -263,6 +270,24 @@
log("updateState: Too soon after last action; not drawing!");
showIncomingCallControls = false;
}
+
+ // b/6765896
+ // If the glowview triggers two hits of the respond-via-sms gadget in
+ // quick succession, it can cause the incoming call widget to show and hide
+ // twice in a row. However, the second hide doesn't get triggered because
+ // we are already attemping to hide. This causes an additional glowview to
+ // stay up above all other screens.
+ // In reality, we shouldn't even be showing incoming-call UI while we are
+ // showing the respond-via-sms popup, so we check for that here.
+ //
+ // TODO: In the future, this entire state machine
+ // should be reworked. Respond-via-sms was stapled onto the current
+ // design (and so were other states) and should be made a first-class
+ // citizen in a new state machine.
+ if (mInCallScreen.isQuickResponseDialogShowing()) {
+ log("updateState: quickResponse visible. Cancel showing incoming call controls.");
+ showIncomingCallControls = false;
+ }
} else {
// Ok, show the regular in-call touch UI (with some exceptions):
if (okToShowInCallControls()) {
@@ -420,6 +445,7 @@
}
return false;
}
+
/**
* Updates the enabledness and "checked" state of the buttons on the
* "inCallControls" panel, based on the current telephony state.
@@ -523,10 +549,12 @@
mHoldButton.setEnabled(true);
mHoldButton.setChecked(inCallControlState.onHold);
mSwapButton.setVisibility(View.GONE);
+ mHoldSwapSpacer.setVisibility(View.VISIBLE);
} else if (inCallControlState.canSwap) {
mSwapButton.setVisibility(View.VISIBLE);
mSwapButton.setEnabled(true);
mHoldButton.setVisibility(View.GONE);
+ mHoldSwapSpacer.setVisibility(View.VISIBLE);
} else {
// Neither "Hold" nor "Swap" is available. This can happen for two
// reasons:
@@ -1057,6 +1085,9 @@
//
// If requested above (i.e. if mShowInCallControlsDuringHidingAnimation is set to true),
// in-call controls will start being shown too.
+ //
+ // TODO: The decision to hide this should be made by the controller
+ // (InCallScreen), and not this view.
hideIncomingCallWidget();
// Regardless of what action the user did, be sure to clear out
@@ -1072,16 +1103,14 @@
* Apply an animation to hide the incoming call widget.
*/
private void hideIncomingCallWidget() {
- // if (DBG) log("hideIncomingCallWidget()...");
+ if (DBG) log("hideIncomingCallWidget()...");
if (mIncomingCallWidget.getVisibility() != View.VISIBLE
|| mIncomingCallWidgetIsFadingOut) {
+ if (DBG) log("Skipping hideIncomingCallWidget action");
// Widget is already hidden or in the process of being hidden
return;
}
- // TODO: remove this once we fixed issue 6603655
- log("hideIncomingCallWidget()");
-
// Hide the incoming call screen with a transition
mIncomingCallWidgetIsFadingOut = true;
ViewPropertyAnimator animator = mIncomingCallWidget.animate();
@@ -1096,6 +1125,7 @@
mInCallControls.setVisibility(View.VISIBLE);
}
}
+
@Override
public void onAnimationEnd(Animator animation) {
if (DBG) log("IncomingCallWidget's hiding animation ended");
@@ -1106,6 +1136,7 @@
mIncomingCallWidgetIsFadingOut = false;
mIncomingCallWidgetShouldBeReset = true;
}
+
@Override
public void onAnimationCancel(Animator animation) {
mIncomingCallWidget.animate().setListener(null);
@@ -1124,12 +1155,11 @@
* Shows the incoming call widget and cancels any animation that may be fading it out.
*/
private void showIncomingCallWidget(Call ringingCall) {
- // if (DBG) log("showIncomingCallWidget()...");
+ if (DBG) log("showIncomingCallWidget()...");
- // TODO: remove this once we fixed issue 6603655
// TODO: wouldn't be ok to suppress this whole request if the widget is already VISIBLE
// and we don't need to reset it?
- log("showIncomingCallWidget(). widget visibility: " + mIncomingCallWidget.getVisibility());
+ // log("showIncomingCallWidget(). widget visibility: " + mIncomingCallWidget.getVisibility());
ViewPropertyAnimator animator = mIncomingCallWidget.animate();
if (animator != null) {
@@ -1179,6 +1209,16 @@
mIncomingCallWidgetShouldBeReset = false;
}
+ // On an incoming call, if the layout is landscape, then align the "incoming call" text
+ // to the left, because the incomingCallWidget (black background with glowing ring)
+ // is aligned to the right and would cover the "incoming call" text.
+ // Note that callStateLabel is within CallCard, outside of the context of InCallTouchUi
+ if (PhoneUtils.isLandscape(this.getContext())) {
+ TextView callStateLabel = (TextView) mIncomingCallWidget
+ .getRootView().findViewById(R.id.callStateLabel);
+ if (callStateLabel != null) callStateLabel.setGravity(Gravity.LEFT);
+ }
+
mIncomingCallWidget.setVisibility(View.VISIBLE);
// Finally, manually trigger a "ping" animation.
diff --git a/src/com/android/phone/InCallUiState.java b/src/com/android/phone/InCallUiState.java
index cfaed99..3b700d7 100644
--- a/src/com/android/phone/InCallUiState.java
+++ b/src/com/android/phone/InCallUiState.java
@@ -393,8 +393,8 @@
* call. See also {@link InCallScreen} for more detail. There is *no* specific specification
* about how this variable will be used.
*
- * @see PhoneApp#setLatestActiveCallOrigin(String)
- * @see PhoneApp#createPhoneEndIntentUsingCallOrigin()
+ * @see PhoneGlobals#setLatestActiveCallOrigin(String)
+ * @see PhoneGlobals#createPhoneEndIntentUsingCallOrigin()
*
* TODO: we should determine some public behavior for this variable.
*/
diff --git a/src/com/android/phone/ManageConferenceUtils.java b/src/com/android/phone/ManageConferenceUtils.java
index e884798..5821754 100644
--- a/src/com/android/phone/ManageConferenceUtils.java
+++ b/src/com/android/phone/ManageConferenceUtils.java
@@ -41,7 +41,7 @@
public class ManageConferenceUtils {
private static final String LOG_TAG = "ManageConferenceUtils";
private static final boolean DBG =
- (PhoneApp.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
+ (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
/**
* CallerInfoAsyncQuery.OnQueryCompleteListener implementation.
@@ -232,7 +232,7 @@
@Override
public void onClick(View v) {
endConferenceConnection(i, connection);
- PhoneApp.getInstance().pokeUserActivity();
+ PhoneGlobals.getInstance().pokeUserActivity();
}
};
endButton.setOnClickListener(endThisConnection);
@@ -242,7 +242,7 @@
@Override
public void onClick(View v) {
separateConferenceConnection(i, connection);
- PhoneApp.getInstance().pokeUserActivity();
+ PhoneGlobals.getInstance().pokeUserActivity();
}
};
separateButton.setOnClickListener(separateThisConnection);
diff --git a/src/com/android/phone/MobileNetworkSettings.java b/src/com/android/phone/MobileNetworkSettings.java
index 2948216..a81d7c2 100644
--- a/src/com/android/phone/MobileNetworkSettings.java
+++ b/src/com/android/phone/MobileNetworkSettings.java
@@ -144,8 +144,8 @@
return true;
} else if (preference == mButtonPreferredNetworkMode) {
//displays the value taken from the Settings.System
- int settingsNetworkMode = android.provider.Settings.Secure.getInt(mPhone.getContext().
- getContentResolver(), android.provider.Settings.Secure.PREFERRED_NETWORK_MODE,
+ int settingsNetworkMode = android.provider.Settings.Global.getInt(mPhone.getContext().
+ getContentResolver(), android.provider.Settings.Global.PREFERRED_NETWORK_MODE,
preferredNetworkMode);
mButtonPreferredNetworkMode.setValue(Integer.toString(settingsNetworkMode));
return true;
@@ -176,8 +176,8 @@
cm.setMobileDataEnabled(mButtonDataEnabled.isChecked());
return true;
} else if (preference == mLteDataServicePref) {
- String tmpl = android.provider.Settings.Secure.getString(getContentResolver(),
- android.provider.Settings.Secure.SETUP_PREPAID_DATA_SERVICE_URL);
+ String tmpl = android.provider.Settings.Global.getString(getContentResolver(),
+ android.provider.Settings.Global.SETUP_PREPAID_DATA_SERVICE_URL);
if (!TextUtils.isEmpty(tmpl)) {
TelephonyManager tm = (TelephonyManager) getSystemService(
Context.TELEPHONY_SERVICE);
@@ -209,7 +209,7 @@
addPreferencesFromResource(R.xml.network_setting);
- mPhone = PhoneApp.getPhone();
+ mPhone = PhoneGlobals.getPhone();
mHandler = new MyHandler();
//get UI object references
@@ -229,8 +229,8 @@
mButtonPreferredNetworkMode.setOnPreferenceChangeListener(this);
//Get the networkMode from Settings.System and displays it
- int settingsNetworkMode = android.provider.Settings.Secure.getInt(mPhone.getContext().
- getContentResolver(),android.provider.Settings.Secure.PREFERRED_NETWORK_MODE,
+ int settingsNetworkMode = android.provider.Settings.Global.getInt(mPhone.getContext().
+ getContentResolver(),android.provider.Settings.Global.PREFERRED_NETWORK_MODE,
preferredNetworkMode);
mButtonPreferredNetworkMode.setValue(Integer.toString(settingsNetworkMode));
mCdmaOptions = new CdmaOptions(this, prefSet, mPhone);
@@ -248,9 +248,9 @@
R.array.preferred_network_mode_choices_lte);
mButtonPreferredNetworkMode.setEntryValues(
R.array.preferred_network_mode_values_lte);
- int settingsNetworkMode = android.provider.Settings.Secure.getInt(
+ int settingsNetworkMode = android.provider.Settings.Global.getInt(
mPhone.getContext().getContentResolver(),
- android.provider.Settings.Secure.PREFERRED_NETWORK_MODE,
+ android.provider.Settings.Global.PREFERRED_NETWORK_MODE,
preferredNetworkMode);
mButtonPreferredNetworkMode.setValue(
Integer.toString(settingsNetworkMode));
@@ -264,8 +264,8 @@
}
final boolean missingDataServiceUrl = TextUtils.isEmpty(
- android.provider.Settings.Secure.getString(getContentResolver(),
- android.provider.Settings.Secure.SETUP_PREPAID_DATA_SERVICE_URL));
+ android.provider.Settings.Global.getString(getContentResolver(),
+ android.provider.Settings.Global.SETUP_PREPAID_DATA_SERVICE_URL));
if (!isLteOnCdma || missingDataServiceUrl) {
prefSet.removePreference(mLteDataServicePref);
} else {
@@ -327,9 +327,9 @@
mButtonPreferredNetworkMode.setValue((String) objValue);
int buttonNetworkMode;
buttonNetworkMode = Integer.valueOf((String) objValue).intValue();
- int settingsNetworkMode = android.provider.Settings.Secure.getInt(
+ int settingsNetworkMode = android.provider.Settings.Global.getInt(
mPhone.getContext().getContentResolver(),
- android.provider.Settings.Secure.PREFERRED_NETWORK_MODE, preferredNetworkMode);
+ android.provider.Settings.Global.PREFERRED_NETWORK_MODE, preferredNetworkMode);
if (buttonNetworkMode != settingsNetworkMode) {
int modemNetworkMode;
switch(buttonNetworkMode) {
@@ -371,8 +371,8 @@
UpdatePreferredNetworkModeSummary(buttonNetworkMode);
- android.provider.Settings.Secure.putInt(mPhone.getContext().getContentResolver(),
- android.provider.Settings.Secure.PREFERRED_NETWORK_MODE,
+ android.provider.Settings.Global.putInt(mPhone.getContext().getContentResolver(),
+ android.provider.Settings.Global.PREFERRED_NETWORK_MODE,
buttonNetworkMode );
//Set the modem network mode
mPhone.setPreferredNetworkType(modemNetworkMode, mHandler
@@ -413,9 +413,9 @@
modemNetworkMode);
}
- int settingsNetworkMode = android.provider.Settings.Secure.getInt(
+ int settingsNetworkMode = android.provider.Settings.Global.getInt(
mPhone.getContext().getContentResolver(),
- android.provider.Settings.Secure.PREFERRED_NETWORK_MODE,
+ android.provider.Settings.Global.PREFERRED_NETWORK_MODE,
preferredNetworkMode);
if (DBG) {
@@ -451,9 +451,9 @@
}
//changes the Settings.System accordingly to modemNetworkMode
- android.provider.Settings.Secure.putInt(
+ android.provider.Settings.Global.putInt(
mPhone.getContext().getContentResolver(),
- android.provider.Settings.Secure.PREFERRED_NETWORK_MODE,
+ android.provider.Settings.Global.PREFERRED_NETWORK_MODE,
settingsNetworkMode );
}
@@ -476,8 +476,8 @@
if (ar.exception == null) {
int networkMode = Integer.valueOf(
mButtonPreferredNetworkMode.getValue()).intValue();
- android.provider.Settings.Secure.putInt(mPhone.getContext().getContentResolver(),
- android.provider.Settings.Secure.PREFERRED_NETWORK_MODE,
+ android.provider.Settings.Global.putInt(mPhone.getContext().getContentResolver(),
+ android.provider.Settings.Global.PREFERRED_NETWORK_MODE,
networkMode );
} else {
mPhone.getPreferredNetworkType(obtainMessage(MESSAGE_GET_PREFERRED_NETWORK_TYPE));
@@ -488,8 +488,8 @@
//set the mButtonPreferredNetworkMode
mButtonPreferredNetworkMode.setValue(Integer.toString(preferredNetworkMode));
//set the Settings.System
- android.provider.Settings.Secure.putInt(mPhone.getContext().getContentResolver(),
- android.provider.Settings.Secure.PREFERRED_NETWORK_MODE,
+ android.provider.Settings.Global.putInt(mPhone.getContext().getContentResolver(),
+ android.provider.Settings.Global.PREFERRED_NETWORK_MODE,
preferredNetworkMode );
//Set the Modem
mPhone.setPreferredNetworkType(preferredNetworkMode,
diff --git a/src/com/android/phone/NetworkSetting.java b/src/com/android/phone/NetworkSetting.java
index 87bd05a..a11ebab 100644
--- a/src/com/android/phone/NetworkSetting.java
+++ b/src/com/android/phone/NetworkSetting.java
@@ -219,7 +219,7 @@
addPreferencesFromResource(R.xml.carrier_select);
- mPhone = PhoneApp.getPhone();
+ mPhone = PhoneGlobals.getPhone();
mNetworkList = (PreferenceGroup) getPreferenceScreen().findPreference(LIST_NETWORKS_KEY);
mNetworkMap = new HashMap<Preference, OperatorInfo>();
@@ -322,7 +322,7 @@
private void displayNetworkQueryFailed(int error) {
String status = getResources().getString(R.string.network_query_error);
- final PhoneApp app = PhoneApp.getInstance();
+ final PhoneGlobals app = PhoneGlobals.getInstance();
app.notificationMgr.postTransientNotification(
NotificationMgr.NETWORK_SELECTION_NOTIFICATION, status);
}
@@ -339,7 +339,7 @@
status = getResources().getString(R.string.connect_later);
}
- final PhoneApp app = PhoneApp.getInstance();
+ final PhoneGlobals app = PhoneGlobals.getInstance();
app.notificationMgr.postTransientNotification(
NotificationMgr.NETWORK_SELECTION_NOTIFICATION, status);
}
@@ -347,7 +347,7 @@
private void displayNetworkSelectionSucceeded() {
String status = getResources().getString(R.string.registration_done);
- final PhoneApp app = PhoneApp.getInstance();
+ final PhoneGlobals app = PhoneGlobals.getInstance();
app.notificationMgr.postTransientNotification(
NotificationMgr.NETWORK_SELECTION_NOTIFICATION, status);
diff --git a/src/com/android/phone/NotificationMgr.java b/src/com/android/phone/NotificationMgr.java
index 530389c..044418c 100644
--- a/src/com/android/phone/NotificationMgr.java
+++ b/src/com/android/phone/NotificationMgr.java
@@ -65,12 +65,12 @@
* framework's NotificationManager, and is used to display status bar
* icons and control other status bar-related behavior.
*
- * @see PhoneApp.notificationMgr
+ * @see PhoneGlobals.notificationMgr
*/
public class NotificationMgr implements CallerInfoAsyncQuery.OnQueryCompleteListener{
private static final String LOG_TAG = "NotificationMgr";
private static final boolean DBG =
- (PhoneApp.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
+ (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
// Do not check in with VDBG = true, since that may write PII to the system log.
private static final boolean VDBG = false;
@@ -95,7 +95,7 @@
/** The singleton NotificationMgr instance. */
private static NotificationMgr sInstance;
- private PhoneApp mApp;
+ private PhoneGlobals mApp;
private Phone mPhone;
private CallManager mCM;
@@ -133,7 +133,7 @@
* Private constructor (this is a singleton).
* @see init()
*/
- private NotificationMgr(PhoneApp app) {
+ private NotificationMgr(PhoneGlobals app) {
mApp = app;
mContext = app;
mNotificationManager =
@@ -155,7 +155,7 @@
* PhoneApp's public "notificationMgr" field, which is why there's no
* getInstance() method here.
*/
- /* package */ static NotificationMgr init(PhoneApp app) {
+ /* package */ static NotificationMgr init(PhoneGlobals app) {
synchronized (NotificationMgr.class) {
if (sInstance == null) {
sInstance = new NotificationMgr(app);
@@ -484,12 +484,12 @@
String name, String number, String type, Drawable photo, Bitmap photoIcon, long date) {
// When the user clicks this notification, we go to the call log.
- final Intent callLogIntent = PhoneApp.createCallLogIntent();
+ final Intent callLogIntent = PhoneGlobals.createCallLogIntent();
// Never display the missed call notification on non-voice-capable
// devices, even if the device does somehow manage to get an
// incoming call.
- if (!PhoneApp.sVoiceCapable) {
+ if (!PhoneGlobals.sVoiceCapable) {
if (DBG) log("notifyMissedCall: non-voice-capable device, not posting notification");
return;
}
@@ -553,11 +553,11 @@
builder.addAction(R.drawable.stat_sys_phone_call,
mContext.getString(R.string.notification_missedCall_call_back),
- PhoneApp.getCallBackPendingIntent(mContext, number));
+ PhoneGlobals.getCallBackPendingIntent(mContext, number));
builder.addAction(R.drawable.ic_text_holo_dark,
mContext.getString(R.string.notification_missedCall_message),
- PhoneApp.getSendSmsFromNotificationPendingIntent(mContext, number));
+ PhoneGlobals.getSendSmsFromNotificationPendingIntent(mContext, number));
if (photoIcon != null) {
builder.setLargeIcon(photoIcon);
@@ -777,7 +777,7 @@
// Never display the "ongoing call" notification on
// non-voice-capable devices, even if the phone is actually
// offhook (like during a non-interactive OTASP call.)
- if (!PhoneApp.sVoiceCapable) {
+ if (!PhoneGlobals.sVoiceCapable) {
if (DBG) log("- non-voice-capable device; suppressing notification.");
return;
}
@@ -892,7 +892,7 @@
// call (see the "fullScreenIntent" field below).
PendingIntent inCallPendingIntent =
PendingIntent.getActivity(mContext, 0,
- PhoneApp.createInCallIntent(), 0);
+ PhoneGlobals.createInCallIntent(), 0);
builder.setContentIntent(inCallPendingIntent);
// Update icon on the left of the notification.
@@ -989,7 +989,16 @@
// and translate that into the elapsedRealtime() timebase.
long callDurationMsec = currentConn.getDurationMillis();
builder.setWhen(System.currentTimeMillis() - callDurationMsec);
- builder.setContentText(mContext.getString(R.string.notification_ongoing_call));
+
+ int contextTextId = R.string.notification_ongoing_call;
+
+ Call call = mCM.getActiveFgCall();
+ if (TelephonyCapabilities.canDistinguishDialingAndConnected(
+ call.getPhone().getPhoneType()) && call.isDialingOrAlerting()) {
+ contextTextId = R.string.notification_dialing;
+ }
+
+ builder.setContentText(mContext.getString(contextTextId));
}
} else if (DBG) {
Log.w(LOG_TAG, "updateInCallNotification: null connection, can't set exp view line 1.");
@@ -1093,7 +1102,7 @@
// TODO: use better asset.
builder.addAction(R.drawable.stat_sys_phone_call_end,
mContext.getText(R.string.notification_action_end_call),
- PhoneApp.createHangUpOngoingCallPendingIntent(mContext));
+ PhoneGlobals.createHangUpOngoingCallPendingIntent(mContext));
}
Notification notification = builder.getNotification();
diff --git a/src/com/android/phone/OtaStartupReceiver.java b/src/com/android/phone/OtaStartupReceiver.java
index 180041f..594b63a 100644
--- a/src/com/android/phone/OtaStartupReceiver.java
+++ b/src/com/android/phone/OtaStartupReceiver.java
@@ -86,7 +86,7 @@
// it's finally OK to start OTA provisioning
if (state.getState() == ServiceState.STATE_IN_SERVICE) {
if (DBG) Log.d(TAG, "call OtaUtils.maybeDoOtaCall after network is available");
- Phone phone = PhoneApp.getPhone();
+ Phone phone = PhoneGlobals.getPhone();
phone.unregisterForServiceStateChanged(this);
OtaUtils.maybeDoOtaCall(mContext, mHandler, MIN_READY);
}
@@ -105,7 +105,13 @@
" mOtaspMode=" + mOtaspMode);
}
- if (!TelephonyCapabilities.supportsOtasp(PhoneApp.getPhone())) {
+ PhoneGlobals globals = PhoneGlobals.getInstanceIfPrimary();
+ if (globals == null) {
+ if (DBG) Log.d(TAG, "Not primary user, nothing to do.");
+ return;
+ }
+
+ if (!TelephonyCapabilities.supportsOtasp(PhoneGlobals.getPhone())) {
if (DBG) Log.d(TAG, "OTASP not supported, nothing to do.");
return;
}
@@ -126,8 +132,8 @@
}
// Delay OTA provisioning if network is not available yet
- PhoneApp app = PhoneApp.getInstance();
- Phone phone = PhoneApp.getPhone();
+ PhoneGlobals app = PhoneGlobals.getInstance();
+ Phone phone = PhoneGlobals.getPhone();
if (app.mCM.getServiceState() != ServiceState.STATE_IN_SERVICE) {
if (DBG) Log.w(TAG, "Network is not ready. Registering to receive notification.");
phone.registerForServiceStateChanged(mHandler, SERVICE_STATE_CHANGED, null);
@@ -158,8 +164,8 @@
Intent intent = new Intent("android.intent.action.DEVICE_INITIALIZATION_WIZARD");
ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(intent,
PackageManager.MATCH_DEFAULT_ONLY);
- boolean provisioned = Settings.Secure.getInt(context.getContentResolver(),
- Settings.Secure.DEVICE_PROVISIONED, 0) != 0;
+ boolean provisioned = Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 0) != 0;
String mode = SystemProperties.get("ro.setupwizard.mode", "REQUIRED");
boolean runningSetupWizard = "REQUIRED".equals(mode) || "OPTIONAL".equals(mode);
if (DBG) {
diff --git a/src/com/android/phone/OtaUtils.java b/src/com/android/phone/OtaUtils.java
index 572c6ac..495df27 100644
--- a/src/com/android/phone/OtaUtils.java
+++ b/src/com/android/phone/OtaUtils.java
@@ -27,6 +27,7 @@
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
+import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -35,6 +36,7 @@
import android.os.Handler;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.UserHandle;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.KeyEvent;
@@ -141,7 +143,7 @@
private InCallScreen mInCallScreen;
private Context mContext;
- private PhoneApp mApplication;
+ private PhoneGlobals mApplication;
private OtaWidgetData mOtaWidgetData;
private ViewGroup mInCallTouchUi; // UI controls for regular calls
private CallCard mCallCard;
@@ -214,7 +216,7 @@
*/
public OtaUtils(Context context, boolean interactive) {
if (DBG) log("OtaUtils constructor...");
- mApplication = PhoneApp.getInstance();
+ mApplication = PhoneGlobals.getInstance();
mContext = context;
mInteractive = interactive;
}
@@ -282,7 +284,7 @@
* @return true if we were able to launch Ota activity or it's not required; false otherwise
*/
public static boolean maybeDoOtaCall(Context context, Handler handler, int request) {
- PhoneApp app = PhoneApp.getInstance();
+ PhoneGlobals app = PhoneGlobals.getInstance();
Phone phone = app.phone;
if (ActivityManager.isRunningInTestHarness()) {
@@ -324,7 +326,7 @@
// Run the OTASP call in "interactive" mode only if
// this is a non-LTE "voice capable" device.
- if (PhoneApp.sVoiceCapable && getLteOnCdmaMode(context) == PhoneConstants.LTE_ON_CDMA_FALSE) {
+ if (PhoneGlobals.sVoiceCapable && getLteOnCdmaMode(context) == PhoneConstants.LTE_ON_CDMA_FALSE) {
if (phoneNeedsActivation
&& (otaShowActivationScreen == OTA_SHOW_ACTIVATION_SCREEN_ON)) {
app.cdmaOtaProvisionData.isOtaCallIntentProcessed = false;
@@ -343,7 +345,12 @@
Intent newIntent = new Intent(ACTION_PERFORM_VOICELESS_CDMA_PROVISIONING);
newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
newIntent.putExtra(EXTRA_VOICELESS_PROVISIONING_OFFER_DONTSHOW, true);
- context.startActivity(newIntent);
+ try {
+ context.startActivity(newIntent);
+ } catch (ActivityNotFoundException e) {
+ loge("No activity Handling PERFORM_VOICELESS_CDMA_PROVISIONING!");
+ return false;
+ }
if (DBG) log("maybeDoOtaCall: non-interactive; activation intent sent.");
} else {
if (DBG) log("maybeDoOtaCall: non-interactive, no need for OTASP.");
@@ -361,7 +368,7 @@
*/
public static void startInteractiveOtasp(Context context) {
if (DBG) log("startInteractiveOtasp()...");
- PhoneApp app = PhoneApp.getInstance();
+ PhoneGlobals app = PhoneGlobals.getInstance();
// There are two ways to start OTASP on voice-capable devices:
//
@@ -426,7 +433,7 @@
*/
public static int startNonInteractiveOtasp(Context context) {
if (DBG) log("startNonInteractiveOtasp()...");
- PhoneApp app = PhoneApp.getInstance();
+ PhoneGlobals app = PhoneGlobals.getInstance();
if (app.otaUtils != null) {
// An OtaUtils instance already exists, presumably from a previous OTASP call.
@@ -442,7 +449,7 @@
// TODO(InCallScreen redesign): This should probably go through
// the CallController, rather than directly calling
// PhoneUtils.placeCall().
- Phone phone = PhoneApp.getPhone();
+ Phone phone = PhoneGlobals.getPhone();
String number = OTASP_NUMBER_NON_INTERACTIVE;
Log.i(LOG_TAG, "startNonInteractiveOtasp: placing call to '" + number + "'...");
int callStatus = PhoneUtils.placeCall(context,
@@ -485,7 +492,7 @@
*/
public static boolean isOtaspCallIntent(Intent intent) {
if (DBG) log("isOtaspCallIntent(" + intent + ")...");
- PhoneApp app = PhoneApp.getInstance();
+ PhoneGlobals app = PhoneGlobals.getInstance();
Phone phone = app.mCM.getDefaultPhone();
if (intent == null) {
@@ -541,7 +548,7 @@
*/
public static void setupOtaspCall(Intent intent) {
if (DBG) log("setupOtaspCall(): preparing for OTASP call to " + intent);
- PhoneApp app = PhoneApp.getInstance();
+ PhoneGlobals app = PhoneGlobals.getInstance();
if (app.otaUtils != null) {
// An OtaUtils instance already exists, presumably from a prior OTASP call.
@@ -707,7 +714,7 @@
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory (Intent.CATEGORY_HOME);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivity(intent);
+ mContext.startActivityAsUser(intent, UserHandle.CURRENT);
return;
}
@@ -1631,4 +1638,8 @@
private static void log(String msg) {
Log.d(LOG_TAG, msg);
}
+
+ private static void loge(String msg) {
+ Log.e(LOG_TAG, msg);
+ }
}
diff --git a/src/com/android/phone/OutgoingCallBroadcaster.java b/src/com/android/phone/OutgoingCallBroadcaster.java
index df14ece..dabffa2 100644
--- a/src/com/android/phone/OutgoingCallBroadcaster.java
+++ b/src/com/android/phone/OutgoingCallBroadcaster.java
@@ -29,6 +29,7 @@
import android.os.Handler;
import android.os.Message;
import android.os.SystemProperties;
+import android.os.UserHandle;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
import android.util.Log;
@@ -60,7 +61,7 @@
private static final String PERMISSION = android.Manifest.permission.PROCESS_OUTGOING_CALLS;
private static final String TAG = "OutgoingCallBroadcaster";
private static final boolean DBG =
- (PhoneApp.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
+ (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
// Do not check in with VDBG = true, since that may write PII to the system log.
private static final boolean VDBG = false;
@@ -145,7 +146,7 @@
number = getResultData();
if (VDBG) Log.v(TAG, "- got number from resultData: '" + number + "'");
- final PhoneApp app = PhoneApp.getInstance();
+ final PhoneGlobals app = PhoneGlobals.getInstance();
// OTASP-specific checks.
// TODO: This should probably all happen in
@@ -390,7 +391,7 @@
final Configuration configuration = getResources().getConfiguration();
// Outgoing phone calls are only allowed on "voice-capable" devices.
- if (!PhoneApp.sVoiceCapable) {
+ if (!PhoneGlobals.sVoiceCapable) {
Log.i(TAG, "This device is detected as non-voice-capable device.");
handleNonVoiceCapable(intent);
return;
@@ -521,7 +522,7 @@
// Also, this ensures the device stays awake while doing the following
// broadcast; technically we should be holding a wake lock here
// as well.
- PhoneApp.getInstance().wakeUpScreen();
+ PhoneGlobals.getInstance().wakeUpScreen();
// If number is null, we're probably trying to call a non-existent voicemail number,
// send an empty flash or something else is fishy. Whatever the problem, there's no
@@ -529,7 +530,7 @@
if (TextUtils.isEmpty(number)) {
if (intent.getBooleanExtra(EXTRA_SEND_EMPTY_FLASH, false)) {
Log.i(TAG, "onCreate: SEND_EMPTY_FLASH...");
- PhoneUtils.sendEmptyFlash(PhoneApp.getPhone());
+ PhoneUtils.sendEmptyFlash(PhoneGlobals.getPhone());
finish();
return;
} else {
@@ -547,7 +548,7 @@
// Initiate the outgoing call, and simultaneously launch the
// InCallScreen to display the in-call UI:
- PhoneApp.getInstance().callController.placeCall(intent);
+ PhoneGlobals.getInstance().callController.placeCall(intent);
// Note we do *not* "return" here, but instead continue and
// send the ACTION_NEW_OUTGOING_CALL broadcast like for any
@@ -559,13 +560,13 @@
// Remember the call origin so that users will be able to see an appropriate screen
// after the phone call. This should affect both phone calls and SIP calls.
- final String callOrigin = intent.getStringExtra(PhoneApp.EXTRA_CALL_ORIGIN);
+ final String callOrigin = intent.getStringExtra(PhoneGlobals.EXTRA_CALL_ORIGIN);
if (callOrigin != null) {
if (DBG) Log.v(TAG, " - Call origin is passed (" + callOrigin + ")");
- PhoneApp.getInstance().setLatestActiveCallOrigin(callOrigin);
+ PhoneGlobals.getInstance().setLatestActiveCallOrigin(callOrigin);
} else {
if (DBG) Log.v(TAG, " - Call origin is not passed. Reset current one.");
- PhoneApp.getInstance().resetLatestActiveCallOrigin();
+ PhoneGlobals.getInstance().resetLatestActiveCallOrigin();
}
// For now, SIP calls will be processed directly without a
@@ -609,7 +610,8 @@
// timeout.
mHandler.sendEmptyMessageDelayed(EVENT_OUTGOING_CALL_TIMEOUT,
OUTGOING_CALL_TIMEOUT_THRESHOLD);
- sendOrderedBroadcast(broadcastIntent, PERMISSION, new OutgoingCallReceiver(),
+ sendOrderedBroadcastAsUser(broadcastIntent, UserHandle.OWNER,
+ PERMISSION, new OutgoingCallReceiver(),
null, // scheduler
Activity.RESULT_OK, // initialCode
number, // initialData: initial value for the result data
diff --git a/src/com/android/phone/PhoneApp.java b/src/com/android/phone/PhoneApp.java
index 10099f8..e3d3fa9 100644
--- a/src/com/android/phone/PhoneApp.java
+++ b/src/com/android/phone/PhoneApp.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2006 The Android Open Source Project
+ * Copyright (C) 2012 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.
@@ -16,1993 +16,34 @@
package com.android.phone;
-import android.app.Activity;
import android.app.Application;
-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.content.ActivityNotFoundException;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
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.LocalPowerManager;
-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.preference.PreferenceManager;
-import android.provider.Settings.System;
-import android.telephony.ServiceState;
-import android.text.TextUtils;
-import android.util.Log;
-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.OtaUtils.CdmaOtaScreenState;
-import com.android.server.sip.SipService;
+import android.os.UserHandle;
/**
* Top-level Application class for the Phone app.
*/
-public class PhoneApp extends Application 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 =
- (PhoneApp.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
- private static final boolean VDBG = (PhoneApp.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 poke lock code (timeout between a user activity and the
- * going to sleep), please refer to {@link com.android.server.PowerManagerService}
- * for additional reference.
- * SHORT uses the short delay for the timeout (SHORT_KEYLIGHT_DELAY, 6 sec)
- * MEDIUM uses the medium delay for the timeout (MEDIUM_KEYLIGHT_DELAY, 15 sec)
- * DEFAULT is the system-wide default delay for the timeout (1 min)
- */
- public enum ScreenTimeoutDuration {
- SHORT,
- MEDIUM,
- DEFAULT
- }
-
- /**
- * 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 PhoneApp 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;
- BluetoothHandsfree mBtHandsfree;
- PhoneInterfaceManager phoneMgr;
- CallManager mCM;
- 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;
-
- /**
- * Timeout setting used by PokeLock.
- *
- * This variable won't be effective when proximity sensor is available in the device.
- *
- * @see ScreenTimeoutDuration
- */
- private ScreenTimeoutDuration mScreenTimeoutDuration = ScreenTimeoutDuration.DEFAULT;
- /**
- * Used to set/unset {@link LocalPowerManager#POKE_LOCK_IGNORE_TOUCH_EVENTS} toward PokeLock.
- *
- * This variable won't be effective when proximity sensor is available in the device.
- */
- private boolean mIgnoreTouchUserActivity = false;
- private final IBinder mPokeLockToken = new Binder();
-
- 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(PhoneApp.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) {
- if (mBtHandsfree == null || !mBtHandsfree.isAudioOn()) {
- 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() &&
- !(mBtHandsfree != null && mBtHandsfree.isAudioOn())) {
- 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 class PhoneApp extends Application {
+ PhoneGlobals mPhoneGlobals;
public PhoneApp() {
- sMe = this;
}
@Override
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 BluetoothHandsree even if device is not voice capable.
- // The device can still support VOIP.
- mBtHandsfree = BluetoothHandsfree.init(this, mCM);
- startService(new Intent(this, BluetoothHeadsetService.class));
- } else {
- // Device is not bluetooth capable
- mBtHandsfree = null;
- }
-
- ringer = Ringer.init(this);
-
- // before registering for phone state changes
- PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
- mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK
- | PowerManager.ACQUIRE_CAUSES_WAKEUP,
- LOG_TAG);
- // lock used to keep the processor awake, when we don't care for the display.
- mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK
- | PowerManager.ON_AFTER_RELEASE, LOG_TAG);
- // Wake lock used to control proximity sensor behavior.
- if ((pm.getSupportedWakeLockFlags()
- & PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK) != 0x0) {
- mProximityWakeLock =
- pm.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);
-
- // 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);
- // ...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);
-
- // 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, mBtHandsfree, new CallLogAsync());
-
- // 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 (UserHandle.myUserId() == 0) {
+ // We are running as the primary user, so should bring up the
+ // global phone state.
+ mPhoneGlobals = new PhoneGlobals(this);
+ mPhoneGlobals.onCreate();
}
-
- 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);
- }
- }
+ }
@Override
public void onConfigurationChanged(Configuration newConfig) {
- if (newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO) {
- mIsHardKeyboardOpen = true;
- } else {
- mIsHardKeyboardOpen = false;
+ if (mPhoneGlobals != null) {
+ mPhoneGlobals.onConfigurationChanged(newConfig);
}
-
- // Update the Proximity sensor based on keyboard state
- updateProximitySensorMode(mCM.getState());
super.onConfigurationChanged(newConfig);
}
-
- /**
- * Returns the singleton instance of the PhoneApp.
- */
- static PhoneApp getInstance() {
- return sMe;
- }
-
- /**
- * Returns the Phone associated with this instance
- */
- static Phone getPhone() {
- return getInstance().phone;
- }
-
- Ringer getRinger() {
- return ringer;
- }
-
- BluetoothHandsfree getBluetoothHandsfree() {
- return mBtHandsfree;
- }
-
- /**
- * 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(PhoneApp.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 how quickly the screen times out.
- *
- * This is no-op when the device supports proximity sensor.
- *
- * The poke lock controls how long it takes before the screen powers
- * down, and therefore has no immediate effect when the current
- * WakeState (see {@link PhoneApp#requestWakeState}) is FULL.
- * If we're in a state where the screen *is* allowed to turn off,
- * though, the poke lock will determine the timeout interval (long or
- * short).
- */
- /* package */ void setScreenTimeout(ScreenTimeoutDuration duration) {
- if (VDBG) Log.d(LOG_TAG, "setScreenTimeout(" + duration + ")...");
-
- // stick with default timeout if we are using the proximity sensor
- if (proximitySensorModeEnabled()) {
- return;
- }
-
- // make sure we don't set the poke lock repeatedly so that we
- // avoid triggering the userActivity calls in
- // PowerManagerService.setPokeLock().
- if (duration == mScreenTimeoutDuration) {
- return;
- }
- mScreenTimeoutDuration = duration;
- updatePokeLock();
- }
-
- /**
- * Update the state of the poke lock held by the phone app,
- * based on the current desired screen timeout and the
- * current "ignore user activity on touch" flag.
- */
- private void updatePokeLock() {
- // Caller must take care of the check. This block is purely for safety.
- if (proximitySensorModeEnabled()) {
- Log.wtf(LOG_TAG, "PokeLock should not be used when proximity sensor is available on"
- + " the device.");
- return;
- }
-
- // This is kind of convoluted, but the basic thing to remember is
- // that the poke lock just sends a message to the screen to tell
- // it to stay on for a while.
- // The default is 0, for a long timeout and should be set that way
- // when we are heading back into a the keyguard / screen off
- // state, and also when we're trying to keep the screen alive
- // while ringing. We'll also want to ignore the cheek events
- // regardless of the timeout duration.
- // The short timeout is really used whenever we want to give up
- // the screen lock, such as when we're in call.
- int pokeLockSetting = 0;
- switch (mScreenTimeoutDuration) {
- case SHORT:
- // Set the poke lock to timeout the display after a short
- // timeout (5s). This ensures that the screen goes to sleep
- // as soon as acceptably possible after we the wake lock
- // has been released.
- pokeLockSetting |= LocalPowerManager.POKE_LOCK_SHORT_TIMEOUT;
- break;
-
- case MEDIUM:
- // Set the poke lock to timeout the display after a medium
- // timeout (15s). This ensures that the screen goes to sleep
- // as soon as acceptably possible after we the wake lock
- // has been released.
- pokeLockSetting |= LocalPowerManager.POKE_LOCK_MEDIUM_TIMEOUT;
- break;
-
- case DEFAULT:
- default:
- // set the poke lock to timeout the display after a long
- // delay by default.
- // TODO: it may be nice to be able to disable cheek presses
- // for long poke locks (emergency dialer, for instance).
- break;
- }
-
- if (mIgnoreTouchUserActivity) {
- pokeLockSetting |= LocalPowerManager.POKE_LOCK_IGNORE_TOUCH_EVENTS;
- }
-
- // Send the request
- try {
- mPowerManagerService.setPokeLock(pokeLockSetting, mPokeLockToken, LOG_TAG);
- } catch (RemoteException e) {
- Log.w(LOG_TAG, "mPowerManagerService.setPokeLock() failed: " + e);
- }
- }
-
- /**
- * 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 PhoneApp#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");
- try {
- mPowerManagerService.userActivityWithForce(SystemClock.uptimeMillis(), false, true);
- } catch (RemoteException ex) {
- // Ignore -- the system process is dead.
- }
- }
- }
- }
-
- /**
- * 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 PhoneApp#requestWakeState} and {@link PhoneApp#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 + "...");
-
- //
- // (1) Set the screen timeout.
- //
- // Note that the "screen timeout" value we determine here is
- // meaningless if the screen is forced on (see (2) below.)
- //
-
- // Historical note: In froyo and earlier, we checked here for a special
- // case: the in-call UI being active, the speaker off, and the DTMF dialpad
- // not visible. In that case, with no touchable UI onscreen at all (for
- // non-prox-sensor devices at least), we could assume the user was probably
- // holding the phone up to their face and *not* actually looking at the
- // screen. So we'd switch to a special screen timeout value
- // (ScreenTimeoutDuration.MEDIUM), purely to save battery life.
- //
- // On current devices, we can rely on the proximity sensor to turn the
- // screen off in this case, so we use the system-wide default timeout
- // unconditionally.
- setScreenTimeout(ScreenTimeoutDuration.DEFAULT);
-
- //
- // (2) 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.
- //
- boolean isRinging = (state == PhoneConstants.State.RINGING);
- boolean isDialing = (phone.getForegroundCall().getState() == Call.State.DIALING);
- boolean showingDisconnectedConnection =
- PhoneUtils.hasDisconnectedConnections(phone) && isShowingCallScreen;
- boolean keepScreenOn = isRinging || isDialing || showingDisconnectedConnection;
- if (DBG) Log.d(LOG_TAG, "updateWakeState: keepScreenOn = " + keepScreenOn
- + " (isRinging " + isRinging
- + ", isDialing " + isDialing
- + ", showingDisc " + showingDisconnectedConnection + ")");
- // keepScreenOn == true means we'll hold a full wake lock:
- requestWakeState(keepScreenOn ? WakeState.FULL : WakeState.SLEEP);
- }
-
- /**
- * Wrapper around the PowerManagerService.preventScreenOn() API.
- * This allows the in-call UI to prevent the screen from turning on
- * even if a subsequent call to updateWakeState() causes us to acquire
- * a full wake lock.
- */
- /* package */ void preventScreenOn(boolean prevent) {
- if (VDBG) Log.d(LOG_TAG, "- preventScreenOn(" + prevent + ")...");
- try {
- mPowerManagerService.preventScreenOn(prevent);
- } catch (RemoteException e) {
- Log.w(LOG_TAG, "mPowerManagerService.preventScreenOn() failed: " + e);
- }
- }
-
- /**
- * Sets or clears the flag that tells the PowerManager that touch
- * (and cheek) events should NOT be considered "user activity".
- *
- * This method is no-op when proximity sensor is available on the device.
- *
- * Since the in-call UI is totally insensitive to touch in most
- * states, we set this flag whenever the InCallScreen is in the
- * foreground. (Otherwise, repeated unintentional touches could
- * prevent the device from going to sleep.)
- *
- * There *are* some some touch events that really do count as user
- * activity, though. For those, we need to manually poke the
- * PowerManager's userActivity method; see pokeUserActivity().
- */
- /* package */ void setIgnoreTouchUserActivity(boolean ignore) {
- if (VDBG) Log.d(LOG_TAG, "setIgnoreTouchUserActivity(" + ignore + ")...");
- // stick with default timeout if we are using the proximity sensor
- if (proximitySensorModeEnabled()) {
- return;
- }
-
- mIgnoreTouchUserActivity = ignore;
- updatePokeLock();
- }
-
- /**
- * Manually pokes the PowerManager's userActivity method. Since we
- * hold the POKE_LOCK_IGNORE_TOUCH_EVENTS poke lock while
- * the InCallScreen is active, 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()...");
- try {
- mPowerManagerService.userActivity(SystemClock.uptimeMillis(), false);
- } catch (RemoteException e) {
- Log.w(LOG_TAG, "mPowerManagerService.userActivity() failed: " + e);
- }
- }
-
- /**
- * 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)
- || ((mBtHandsfree != null) && mBtHandsfree.isAudioOn())
- || 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();
- if (mBtHandsfree != null) {
- mBtHandsfree.updateBtHandsfreeAfterRadioTechnologyChange();
- }
- 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(PhoneApp.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.sendBroadcast(intent);
- }
-
- 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);
- sendBroadcast(ttyModeChanged);
-
- 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));
- }
-
- /* package */ void clearUserActivityTimeout() {
- try {
- mPowerManagerService.clearUserActivityTimeout(SystemClock.uptimeMillis(),
- 10*1000 /* 10 sec */);
- } catch (RemoteException ex) {
- // System process is dead.
- }
- }
-
- /**
- * "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.contacts";
- private static final String ALLOWED_EXTRA_CALL_ORIGIN =
- "com.android.contacts.activities.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.contacts", 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 PhoneApp.createCallLogIntent();
- }
- }
}
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
new file mode 100644
index 0000000..acfdd7e
--- /dev/null
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -0,0 +1,1850 @@
+/*
+ * 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.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;
+ 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);
+
+ // 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);
+ // ...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);
+
+ // 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, new CallLogAsync());
+
+ // 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();
+ 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.contacts";
+ private static final String ALLOWED_EXTRA_CALL_ORIGIN =
+ "com.android.contacts.activities.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.contacts", 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;
+ }
+ };
+}
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index a5bb130..441270a 100644
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -16,6 +16,7 @@
package com.android.phone;
+import android.app.ActivityManager;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
@@ -27,11 +28,12 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.Process;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.telephony.NeighboringCellInfo;
import android.telephony.CellInfo;
import android.telephony.ServiceState;
-import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
@@ -50,7 +52,8 @@
*/
public class PhoneInterfaceManager extends ITelephony.Stub {
private static final String LOG_TAG = "PhoneInterfaceManager";
- private static final boolean DBG = (PhoneApp.DBG_LEVEL >= 2);
+ private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
+ private static final boolean DBG_LOC = false;
// Message codes used with mMainThreadHandler
private static final int CMD_HANDLE_PIN_MMI = 1;
@@ -63,7 +66,7 @@
/** The singleton instance. */
private static PhoneInterfaceManager sInstance;
- PhoneApp mApp;
+ PhoneGlobals mApp;
Phone mPhone;
CallManager mCM;
MainThreadHandler mMainThreadHandler;
@@ -213,7 +216,7 @@
* Initialize the singleton PhoneInterfaceManager instance.
* This is only done once, at startup, from PhoneApp.onCreate().
*/
- /* package */ static PhoneInterfaceManager init(PhoneApp app, Phone phone) {
+ /* package */ static PhoneInterfaceManager init(PhoneGlobals app, Phone phone) {
synchronized (PhoneInterfaceManager.class) {
if (sInstance == null) {
sInstance = new PhoneInterfaceManager(app, phone);
@@ -225,10 +228,10 @@
}
/** Private constructor; @see init() */
- private PhoneInterfaceManager(PhoneApp app, Phone phone) {
+ private PhoneInterfaceManager(PhoneGlobals app, Phone phone) {
mApp = app;
mPhone = phone;
- mCM = PhoneApp.getInstance().mCM;
+ mCM = PhoneGlobals.getInstance().mCM;
mMainThreadHandler = new MainThreadHandler();
publish();
}
@@ -283,7 +286,7 @@
private boolean showCallScreenInternal(boolean specifyInitialDialpadState,
boolean initialDialpadState) {
- if (!PhoneApp.sVoiceCapable) {
+ if (!PhoneGlobals.sVoiceCapable) {
// Never allow the InCallScreen to appear on data-only devices.
return false;
}
@@ -295,9 +298,9 @@
try {
Intent intent;
if (specifyInitialDialpadState) {
- intent = PhoneApp.createInCallIntent(initialDialpadState);
+ intent = PhoneGlobals.createInCallIntent(initialDialpadState);
} else {
- intent = PhoneApp.createInCallIntent();
+ intent = PhoneGlobals.createInCallIntent();
}
try {
mApp.startActivity(intent);
@@ -420,7 +423,7 @@
public boolean isSimPinEnabled() {
enforceReadPermission();
- return (PhoneApp.getInstance().isSimPinEnabled());
+ return (PhoneGlobals.getInstance().isSimPinEnabled());
}
public boolean supplyPin(String pin) {
@@ -596,6 +599,7 @@
return DefaultPhoneNotifier.convertDataActivityState(mPhone.getDataActivityState());
}
+ @Override
public Bundle getCellLocation() {
try {
mApp.enforceCallingOrSelfPermission(
@@ -607,23 +611,33 @@
mApp.enforceCallingOrSelfPermission(
android.Manifest.permission.ACCESS_COARSE_LOCATION, null);
}
- Bundle data = new Bundle();
- mPhone.getCellLocation().fillInNotifierBundle(data);
- return data;
+
+ if (checkIfCallerIsSelfOrForegoundUser()) {
+ if (DBG_LOC) log("getCellLocation: is active user");
+ Bundle data = new Bundle();
+ mPhone.getCellLocation().fillInNotifierBundle(data);
+ return data;
+ } else {
+ if (DBG_LOC) log("getCellLocation: suppress non-active user");
+ return null;
+ }
}
+ @Override
public void enableLocationUpdates() {
mApp.enforceCallingOrSelfPermission(
android.Manifest.permission.CONTROL_LOCATION_UPDATES, null);
mPhone.enableLocationUpdates();
}
+ @Override
public void disableLocationUpdates() {
mApp.enforceCallingOrSelfPermission(
android.Manifest.permission.CONTROL_LOCATION_UPDATES, null);
mPhone.disableLocationUpdates();
}
+ @Override
@SuppressWarnings("unchecked")
public List<NeighboringCellInfo> getNeighboringCellInfo() {
try {
@@ -638,19 +652,26 @@
android.Manifest.permission.ACCESS_COARSE_LOCATION, null);
}
- ArrayList<NeighboringCellInfo> cells = null;
+ if (checkIfCallerIsSelfOrForegoundUser()) {
+ if (DBG_LOC) log("getNeighboringCellInfo: is active user");
- try {
- cells = (ArrayList<NeighboringCellInfo>) sendRequest(
- CMD_HANDLE_NEIGHBORING_CELL, null);
- } catch (RuntimeException e) {
- Log.e(LOG_TAG, "getNeighboringCellInfo " + e);
+ ArrayList<NeighboringCellInfo> cells = null;
+
+ try {
+ cells = (ArrayList<NeighboringCellInfo>) sendRequest(
+ CMD_HANDLE_NEIGHBORING_CELL, null);
+ } catch (RuntimeException e) {
+ Log.e(LOG_TAG, "getNeighboringCellInfo " + e);
+ }
+ return cells;
+ } else {
+ if (DBG_LOC) log("getNeighboringCellInfo: suppress non-active user");
+ return null;
}
-
- return (List <NeighboringCellInfo>) cells;
}
+ @Override
public List<CellInfo> getAllCellInfo() {
try {
mApp.enforceCallingOrSelfPermission(
@@ -663,14 +684,51 @@
android.Manifest.permission.ACCESS_COARSE_LOCATION, null);
}
- // TODO return cell info list got from mPhone
- return null;
+ if (checkIfCallerIsSelfOrForegoundUser()) {
+ if (DBG_LOC) log("getAllCellInfo: is active user");
+ return mPhone.getAllCellInfo();
+ } else {
+ if (DBG_LOC) log("getAllCellInfo: suppress non-active user");
+ return null;
+ }
}
//
// Internal helper methods.
//
+ private boolean checkIfCallerIsSelfOrForegoundUser() {
+ boolean ok;
+
+ boolean self = Binder.getCallingUid() == Process.myUid();
+ if (!self) {
+ // Get the caller's user id then clear the calling identity
+ // which will be restored in the finally clause.
+ int callingUser = UserHandle.getCallingUserId();
+ long ident = Binder.clearCallingIdentity();
+
+ try {
+ // With calling identity cleared the current user is the foreground user.
+ int foregroundUser = ActivityManager.getCurrentUser();
+ ok = (foregroundUser == callingUser);
+ if (DBG_LOC) {
+ log("checkIfCallerIsSelfOrForegoundUser: foregroundUser=" + foregroundUser
+ + " callingUser=" + callingUser + " ok=" + ok);
+ }
+ } catch (Exception ex) {
+ if (DBG_LOC) loge("checkIfCallerIsSelfOrForegoundUser: Exception ex=" + ex);
+ ok = false;
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ } else {
+ if (DBG_LOC) log("checkIfCallerIsSelfOrForegoundUser: is self");
+ ok = true;
+ }
+ if (DBG_LOC) log("checkIfCallerIsSelfOrForegoundUser: ret=" + ok);
+ return ok;
+ }
+
/**
* Make sure the caller has the READ_PHONE_STATE permission.
*
@@ -713,6 +771,10 @@
Log.d(LOG_TAG, "[PhoneIntfMgr] " + msg);
}
+ private void loge(String msg) {
+ Log.e(LOG_TAG, "[PhoneIntfMgr] " + msg);
+ }
+
public int getActivePhoneType() {
return mPhone.getPhoneType();
}
diff --git a/src/com/android/phone/PhoneUtils.java b/src/com/android/phone/PhoneUtils.java
index 616bd40..b71ae35 100644
--- a/src/com/android/phone/PhoneUtils.java
+++ b/src/com/android/phone/PhoneUtils.java
@@ -19,6 +19,7 @@
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
+import android.bluetooth.IBluetoothHeadsetPhone;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
@@ -27,6 +28,7 @@
import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.net.Uri;
@@ -75,7 +77,7 @@
*/
public class PhoneUtils {
private static final String LOG_TAG = "PhoneUtils";
- private static final boolean DBG = (PhoneApp.DBG_LEVEL >= 2);
+ private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
// Do not check in with VDBG = true, since that may write PII to the system log.
private static final boolean VDBG = false;
@@ -244,7 +246,7 @@
*/
/* package */ static boolean answerCall(Call ringing) {
log("answerCall(" + ringing + ")...");
- final PhoneApp app = PhoneApp.getInstance();
+ final PhoneGlobals app = PhoneGlobals.getInstance();
// If the ringer is currently ringing and/or vibrating, stop it
// right now (before actually answering the call.)
@@ -253,7 +255,7 @@
final Phone phone = ringing.getPhone();
final boolean phoneIsCdma = (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA);
boolean answered = false;
- BluetoothHandsfree bluetoothHandsfree = null;
+ IBluetoothHeadsetPhone btPhone = null;
if (phoneIsCdma) {
// Stop any signalInfo tone being played when a Call waiting gets answered
@@ -283,14 +285,18 @@
// drops off
app.cdmaPhoneCallState.setAddCallMenuStateAfterCallWaiting(true);
- // If a BluetoothHandsfree is valid we need to set the second call state
+ // If a BluetoothPhoneService is valid we need to set the second call state
// so that the Bluetooth client can update the Call state correctly when
// a call waiting is answered from the Phone.
- bluetoothHandsfree = app.getBluetoothHandsfree();
- if (bluetoothHandsfree != null) {
- bluetoothHandsfree.cdmaSetSecondCallState(true);
+ btPhone = app.getBluetoothPhoneService();
+ if (btPhone != null) {
+ try {
+ btPhone.cdmaSetSecondCallState(true);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, Log.getStackTraceString(new Throwable()));
+ }
}
- }
+ }
}
final boolean isRealIncomingCall = isRealIncomingCall(ringing.getState());
@@ -316,7 +322,7 @@
// - we did not activate speaker by ourselves during the process above, and
// - Bluetooth headset is not in use.
if (isRealIncomingCall && !speakerActivated && isSpeakerOn(app)
- && !(bluetoothHandsfree != null && bluetoothHandsfree.isAudioOn())) {
+ && !app.isBluetoothHeadsetAudioOn()) {
// This is not an error but might cause users' confusion. Add log just in case.
Log.i(LOG_TAG, "Forcing speaker off due to new incoming call...");
turnOnSpeaker(app, false, true);
@@ -325,11 +331,15 @@
Log.w(LOG_TAG, "answerCall: caught " + ex, ex);
if (phoneIsCdma) {
- // restore the cdmaPhoneCallState and bthf.cdmaSetSecondCallState:
+ // restore the cdmaPhoneCallState and btPhone.cdmaSetSecondCallState:
app.cdmaPhoneCallState.setCurrentCallState(
app.cdmaPhoneCallState.getPreviousCallState());
- if (bluetoothHandsfree != null) {
- bluetoothHandsfree.cdmaSetSecondCallState(false);
+ if (btPhone != null) {
+ try {
+ btPhone.cdmaSetSecondCallState(false);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, Log.getStackTraceString(new Throwable()));
+ }
}
}
}
@@ -398,7 +408,7 @@
// For Call waiting we DO NOT call the conventional hangup(call) function
// as in CDMA we just want to hangup the Call waiting connection.
log("hangupRingingCall(): CDMA-specific call-waiting hangup");
- final CallNotifier notifier = PhoneApp.getInstance().notifier;
+ final CallNotifier notifier = PhoneGlobals.getInstance().notifier;
notifier.sendCdmaCallWaitingReject();
return true;
} else {
@@ -464,7 +474,7 @@
*/
static boolean hangup(Call call) {
try {
- CallManager cm = PhoneApp.getInstance().mCM;
+ CallManager cm = PhoneGlobals.getInstance().mCM;
if (call.getState() == Call.State.ACTIVE && cm.hasActiveBgCall()) {
// handle foreground call hangup while there is background call
@@ -560,7 +570,7 @@
* </pre>
* @param app The phone instance.
*/
- private static void updateCdmaCallStateOnNewOutgoingCall(PhoneApp app) {
+ private static void updateCdmaCallStateOnNewOutgoingCall(PhoneGlobals app) {
if (app.cdmaPhoneCallState.getCurrentCallState() ==
CdmaPhoneCallState.PhoneCallState.IDLE) {
// This is the first outgoing call. Set the Phone Call State to ACTIVE
@@ -607,7 +617,7 @@
+ ", GW: " + (gatewayUri != null ? "non-null" : "null")
+ ", emergency? " + isEmergencyCall);
}
- final PhoneApp app = PhoneApp.getInstance();
+ final PhoneGlobals app = PhoneGlobals.getInstance();
boolean useGateway = false;
if (null != gatewayUri &&
@@ -736,9 +746,8 @@
final boolean speakerActivated = activateSpeakerIfDocked(phone);
// See also similar logic in answerCall().
- final BluetoothHandsfree bluetoothHandsfree = app.getBluetoothHandsfree();
if (initiallyIdle && !speakerActivated && isSpeakerOn(app)
- && !(bluetoothHandsfree != null && bluetoothHandsfree.isAudioOn())) {
+ && !app.isBluetoothHeadsetAudioOn()) {
// This is not an error but might cause users' confusion. Add log just in case.
Log.i(LOG_TAG, "Forcing speaker off when initiating a new outgoing call...");
PhoneUtils.turnOnSpeaker(app, false, true);
@@ -790,7 +799,7 @@
static void switchHoldingAndActive(Call heldCall) {
log("switchHoldingAndActive()...");
try {
- CallManager cm = PhoneApp.getInstance().mCM;
+ CallManager cm = PhoneGlobals.getInstance().mCM;
if (heldCall.isIdle()) {
// no heldCall, so it is to hold active call
cm.switchHoldingAndActive(cm.getFgPhone().getBackgroundCall());
@@ -809,7 +818,7 @@
* foreground call.
*/
static Boolean restoreMuteState() {
- Phone phone = PhoneApp.getInstance().mCM.getFgPhone();
+ Phone phone = PhoneGlobals.getInstance().mCM.getFgPhone();
//get the earliest connection
Connection c = phone.getForegroundCall().getEarliestConnection();
@@ -846,14 +855,14 @@
}
static void mergeCalls() {
- mergeCalls(PhoneApp.getInstance().mCM);
+ mergeCalls(PhoneGlobals.getInstance().mCM);
}
static void mergeCalls(CallManager cm) {
int phoneType = cm.getFgPhone().getPhoneType();
if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
log("mergeCalls(): CDMA...");
- PhoneApp app = PhoneApp.getInstance();
+ PhoneGlobals app = PhoneGlobals.getInstance();
if (app.cdmaPhoneCallState.getCurrentCallState()
== CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
// Set the Phone Call State to conference
@@ -1000,7 +1009,7 @@
static void displayMMIComplete(final Phone phone, Context context, final MmiCode mmiCode,
Message dismissCallbackMessage,
AlertDialog previousAlert) {
- final PhoneApp app = PhoneApp.getInstance();
+ final PhoneGlobals app = PhoneGlobals.getInstance();
CharSequence text;
int title = 0; // title for the progress dialog, if needed.
MmiCode.State state = mmiCode.getState();
@@ -1297,7 +1306,7 @@
return actualNumberToDial;
}
- return getNumberFromIntent(PhoneApp.getInstance(), intent);
+ return getNumberFromIntent(PhoneGlobals.getInstance(), intent);
}
/**
@@ -1790,7 +1799,7 @@
// you're in a 3-way call, all we can do is display the "generic"
// state of the UI.) So as far as the in-call UI is concerned,
// Conference corresponds to generic display.
- final PhoneApp app = PhoneApp.getInstance();
+ final PhoneGlobals app = PhoneGlobals.getInstance();
int phoneType = call.getPhone().getPhoneType();
if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
CdmaPhoneCallState.PhoneCallState state = app.cdmaPhoneCallState.getCurrentCallState();
@@ -1832,7 +1841,7 @@
* This is just a wrapper around the ACTION_DIAL intent.
*/
/* package */ static boolean startNewCall(final CallManager cm) {
- final PhoneApp app = PhoneApp.getInstance();
+ final PhoneGlobals app = PhoneGlobals.getInstance();
// Sanity-check that this is OK given the current state of the phone.
if (!okToAddCall(cm)) {
@@ -1878,7 +1887,7 @@
*/
/* package */ static void turnOnSpeaker(Context context, boolean flag, boolean store) {
if (DBG) log("turnOnSpeaker(flag=" + flag + ", store=" + store + ")...");
- final PhoneApp app = PhoneApp.getInstance();
+ final PhoneGlobals app = PhoneGlobals.getInstance();
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
audioManager.setSpeakerphoneOn(flag);
@@ -1986,7 +1995,7 @@
*
*/
static void setMute(boolean muted) {
- CallManager cm = PhoneApp.getInstance().mCM;
+ CallManager cm = PhoneGlobals.getInstance().mCM;
// make the call to mute the audio
setMuteInternal(cm.getFgPhone(), muted);
@@ -2005,7 +2014,7 @@
* Internally used muting function.
*/
private static void setMuteInternal(Phone phone, boolean muted) {
- final PhoneApp app = PhoneApp.getInstance();
+ final PhoneGlobals app = PhoneGlobals.getInstance();
Context context = phone.getContext();
boolean routeToAudioManager =
context.getResources().getBoolean(R.bool.send_mic_mute_to_AudioManager);
@@ -2026,7 +2035,7 @@
* foreground call
*/
static boolean getMute() {
- final PhoneApp app = PhoneApp.getInstance();
+ final PhoneGlobals app = PhoneGlobals.getInstance();
boolean routeToAudioManager =
app.getResources().getBoolean(R.bool.send_mic_mute_to_AudioManager);
@@ -2040,7 +2049,7 @@
}
/* package */ static void setAudioMode() {
- setAudioMode(PhoneApp.getInstance().mCM);
+ setAudioMode(PhoneGlobals.getInstance().mCM);
}
/**
@@ -2049,7 +2058,7 @@
/* package */ static void setAudioMode(CallManager cm) {
if (DBG) Log.d(LOG_TAG, "setAudioMode()..." + cm.getState());
- Context context = PhoneApp.getInstance();
+ Context context = PhoneGlobals.getInstance();
AudioManager audioManager = (AudioManager)
context.getSystemService(Context.AUDIO_SERVICE);
int modeBefore = audioManager.getMode();
@@ -2087,7 +2096,7 @@
*/
/* package */ static boolean handleHeadsetHook(Phone phone, KeyEvent event) {
if (DBG) log("handleHeadsetHook()..." + event.getAction() + " " + event.getRepeatCount());
- final PhoneApp app = PhoneApp.getInstance();
+ final PhoneGlobals app = PhoneGlobals.getInstance();
// If the phone is totally idle, we ignore HEADSETHOOK events
// (and instead let them fall through to the media player.)
@@ -2140,7 +2149,7 @@
Connection c = phone.getForegroundCall().getLatestConnection();
// If it is NOT an emg #, toggle the mute state. Otherwise, ignore the hook.
if (c != null && !PhoneNumberUtils.isLocalEmergencyNumber(c.getAddress(),
- PhoneApp.getInstance())) {
+ PhoneGlobals.getInstance())) {
if (getMute()) {
if (DBG) log("handleHeadsetHook: UNmuting...");
setMute(false);
@@ -2212,7 +2221,7 @@
if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
// CDMA: "Swap" is enabled only when the phone reaches a *generic*.
// state by either accepting a Call Waiting or by merging two calls
- PhoneApp app = PhoneApp.getInstance();
+ PhoneGlobals app = PhoneGlobals.getInstance();
return (app.cdmaPhoneCallState.getCurrentCallState()
== CdmaPhoneCallState.PhoneCallState.CONF_CALL);
} else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM)
@@ -2238,7 +2247,7 @@
int phoneType = cm.getFgPhone().getPhoneType();
if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
// CDMA: "Merge" is enabled only when the user is in a 3Way call.
- PhoneApp app = PhoneApp.getInstance();
+ PhoneGlobals app = PhoneGlobals.getInstance();
return ((app.cdmaPhoneCallState.getCurrentCallState()
== CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
&& !app.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing());
@@ -2271,7 +2280,7 @@
// CDMA: "Add call" button is only enabled when:
// - ForegroundCall is in ACTIVE state
// - After 30 seconds of user Ignoring/Missing a Call Waiting call.
- PhoneApp app = PhoneApp.getInstance();
+ PhoneGlobals app = PhoneGlobals.getInstance();
return ((fgCallState == Call.State.ACTIVE)
&& (app.cdmaPhoneCallState.getAddCallMenuStateAfterCallWaiting()));
} else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM)
@@ -2518,12 +2527,11 @@
if (DBG) log("activateSpeakerIfDocked()...");
boolean activated = false;
- if (PhoneApp.mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
+ if (PhoneGlobals.mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
if (DBG) log("activateSpeakerIfDocked(): In a dock -> may need to turn on speaker.");
- PhoneApp app = PhoneApp.getInstance();
- BluetoothHandsfree bthf = app.getBluetoothHandsfree();
+ PhoneGlobals app = PhoneGlobals.getInstance();
- if (!app.isHeadsetPlugged() && !(bthf != null && bthf.isAudioOn())) {
+ if (!app.isHeadsetPlugged() && !app.isBluetoothHeadsetAudioOn()) {
turnOnSpeaker(phone.getContext(), true, true);
activated = true;
}
@@ -2595,12 +2603,12 @@
* meaning the call is the first real incoming call the phone is having.
*/
public static boolean isRealIncomingCall(Call.State state) {
- return (state == Call.State.INCOMING && !PhoneApp.getInstance().mCM.hasActiveFgCall());
+ return (state == Call.State.INCOMING && !PhoneGlobals.getInstance().mCM.hasActiveFgCall());
}
private static boolean sVoipSupported = false;
static {
- PhoneApp app = PhoneApp.getInstance();
+ PhoneGlobals app = PhoneGlobals.getInstance();
sVoipSupported = SipManager.isVoipSupported(app)
&& app.getResources().getBoolean(com.android.internal.R.bool.config_built_in_sip_phone)
&& app.getResources().getBoolean(com.android.internal.R.bool.config_voice_capable);
@@ -2661,7 +2669,7 @@
//
/* package */ static void dumpCallState(Phone phone) {
- PhoneApp app = PhoneApp.getInstance();
+ PhoneGlobals app = PhoneGlobals.getInstance();
Log.d(LOG_TAG, "dumpCallState():");
Log.d(LOG_TAG, "- Phone: " + phone + ", name = " + phone.getPhoneName()
+ ", state = " + phone.getState());
@@ -2733,7 +2741,7 @@
static void dumpCallManager() {
Call call;
- CallManager cm = PhoneApp.getInstance().mCM;
+ CallManager cm = PhoneGlobals.getInstance().mCM;
StringBuilder b = new StringBuilder(128);
@@ -2795,4 +2803,12 @@
Log.d(LOG_TAG, "############## END dumpCallManager() ###############");
}
+
+ /**
+ * @return if the context is in landscape orientation.
+ */
+ public static boolean isLandscape(Context context) {
+ return context.getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_LANDSCAPE;
+ }
}
diff --git a/src/com/android/phone/Profiler.java b/src/com/android/phone/Profiler.java
index 186d126..234073c 100644
--- a/src/com/android/phone/Profiler.java
+++ b/src/com/android/phone/Profiler.java
@@ -26,7 +26,7 @@
* Profiling utilities for the Phone app.
*/
public class Profiler {
- private static final String LOG_TAG = PhoneApp.LOG_TAG;
+ private static final String LOG_TAG = PhoneGlobals.LOG_TAG;
// Let the compiler optimize all this code out unless we're actively
// doing profiling runs.
diff --git a/src/com/android/phone/RespondViaSmsManager.java b/src/com/android/phone/RespondViaSmsManager.java
index bc2ee7b..003c56f 100644
--- a/src/com/android/phone/RespondViaSmsManager.java
+++ b/src/com/android/phone/RespondViaSmsManager.java
@@ -49,12 +49,13 @@
/**
* Helper class to manage the "Respond via SMS" feature for incoming calls.
+ *
* @see InCallScreen.internalRespondViaSms()
*/
public class RespondViaSmsManager {
private static final String TAG = "RespondViaSmsManager";
private static final boolean DBG =
- (PhoneApp.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
+ (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
// Do not check in with VDBG = true, since that may write PII to the system log.
private static final boolean VDBG = false;
@@ -118,6 +119,13 @@
public void showRespondViaSmsPopup(Call ringingCall) {
if (DBG) log("showRespondViaSmsPopup()...");
+ // Very quick succession of clicks can cause this to run twice.
+ // Stop here to avoid creating more than one popup.
+ if (isShowingPopup()) {
+ if (DBG) log("Skip showing popup when one is already shown.");
+ return;
+ }
+
ListView lv = new ListView(mInCallScreen);
// Refresh the array of "canned responses".
@@ -259,10 +267,10 @@
dismissPopup();
- final PhoneConstants.State state = PhoneApp.getInstance().mCM.getState();
+ final PhoneConstants.State state = PhoneGlobals.getInstance().mCM.getState();
if (state == PhoneConstants.State.IDLE) {
// There's no other phone call to interact. Exit the entire in-call screen.
- PhoneApp.getInstance().dismissCallScreen();
+ PhoneGlobals.getInstance().dismissCallScreen();
} else {
// The user is still in the middle of other phone calls, so we should keep the
// in-call screen.
@@ -288,11 +296,11 @@
dismissPopup();
- final PhoneConstants.State state = PhoneApp.getInstance().mCM.getState();
+ final PhoneConstants.State state = PhoneGlobals.getInstance().mCM.getState();
if (state == PhoneConstants.State.IDLE) {
// This means the incoming call is already hung up when the user chooses not to
// use "Respond via SMS" feature. Let's just exit the whole in-call screen.
- PhoneApp.getInstance().dismissCallScreen();
+ PhoneGlobals.getInstance().dismissCallScreen();
} else {
// If the user cancels the popup, this presumably means that
@@ -303,7 +311,7 @@
// call UI.
// This will have no effect if the incoming call isn't still ringing.
- PhoneApp.getInstance().notifier.restartRinger();
+ PhoneGlobals.getInstance().notifier.restartRinger();
// We hid the GlowPadView widget way back in
// InCallTouchUi.onTrigger(), when the user first selected
diff --git a/src/com/android/phone/Ringer.java b/src/com/android/phone/Ringer.java
index edbcdfa..e97716f 100644
--- a/src/com/android/phone/Ringer.java
+++ b/src/com/android/phone/Ringer.java
@@ -41,7 +41,7 @@
public class Ringer {
private static final String LOG_TAG = "Ringer";
private static final boolean DBG =
- (PhoneApp.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
+ (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
private static final int PLAY_RING_ONCE = 1;
private static final int STOP_RING = 3;
@@ -149,7 +149,7 @@
synchronized (this) {
try {
- if (PhoneApp.getInstance().showBluetoothIndication()) {
+ if (PhoneGlobals.getInstance().showBluetoothIndication()) {
mPowerManager.setAttentionLight(true, 0x000000ff);
} else {
mPowerManager.setAttentionLight(true, 0x00ffffff);
diff --git a/src/com/android/phone/SipBroadcastReceiver.java b/src/com/android/phone/SipBroadcastReceiver.java
index 0717c72..c33c5e1 100644
--- a/src/com/android/phone/SipBroadcastReceiver.java
+++ b/src/com/android/phone/SipBroadcastReceiver.java
@@ -91,7 +91,7 @@
}
private void takeCall(Intent intent) {
- Context phoneContext = PhoneApp.getInstance();
+ Context phoneContext = PhoneGlobals.getInstance();
try {
SipAudioCall sipAudioCall = SipManager.newInstance(phoneContext)
.takeAudioCall(intent, null);
@@ -107,7 +107,7 @@
}
private void registerAllProfiles() {
- final Context context = PhoneApp.getInstance();
+ final Context context = PhoneGlobals.getInstance();
new Thread(new Runnable() {
public void run() {
SipManager sipManager = SipManager.newInstance(context);
diff --git a/src/com/android/phone/SipCallOptionHandler.java b/src/com/android/phone/SipCallOptionHandler.java
index 64d18c5..500f322 100644
--- a/src/com/android/phone/SipCallOptionHandler.java
+++ b/src/com/android/phone/SipCallOptionHandler.java
@@ -64,7 +64,7 @@
CompoundButton.OnCheckedChangeListener {
static final String TAG = "SipCallOptionHandler";
private static final boolean DBG =
- (PhoneApp.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
+ (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
static final int DIALOG_SELECT_PHONE_TYPE = 0;
static final int DIALOG_SELECT_OUTGOING_SIP_PHONE = 1;
@@ -147,7 +147,7 @@
Uri uri = mIntent.getData();
String scheme = uri.getScheme();
mNumber = PhoneNumberUtils.getNumberFromIntent(mIntent, this);
- boolean isInCellNetwork = PhoneApp.getInstance().phoneMgr.isRadioOn();
+ boolean isInCellNetwork = PhoneGlobals.getInstance().phoneMgr.isRadioOn();
boolean isKnownCallScheme = Constants.SCHEME_TEL.equals(scheme)
|| Constants.SCHEME_SIP.equals(scheme);
boolean isRegularCall = Constants.SCHEME_TEL.equals(scheme)
@@ -351,7 +351,7 @@
}
private void createSipPhoneIfNeeded(SipProfile p) {
- CallManager cm = PhoneApp.getInstance().mCM;
+ CallManager cm = PhoneGlobals.getInstance().mCM;
if (PhoneUtils.getSipPhoneFromUri(cm, p.getUriString()) != null) return;
// Create the phone since we can not find it in CallManager
@@ -392,7 +392,7 @@
return;
} else {
// Woo hoo -- it's finally OK to initiate the outgoing call!
- PhoneApp.getInstance().callController.placeCall(mIntent);
+ PhoneGlobals.getInstance().callController.placeCall(mIntent);
}
finish();
}
diff --git a/src/com/android/phone/SipUtil.java b/src/com/android/phone/SipUtil.java
index 85a3110..a901d58 100644
--- a/src/com/android/phone/SipUtil.java
+++ b/src/com/android/phone/SipUtil.java
@@ -26,7 +26,7 @@
}
public static PendingIntent createIncomingCallPendingIntent() {
- Context phoneContext = PhoneApp.getInstance();
+ Context phoneContext = PhoneGlobals.getInstance();
Intent intent = new Intent(phoneContext, SipBroadcastReceiver.class);
intent.setAction(SipManager.ACTION_SIP_INCOMING_CALL);
return PendingIntent.getBroadcast(phoneContext, 0, intent,
diff --git a/src/com/android/phone/SpecialCharSequenceMgr.java b/src/com/android/phone/SpecialCharSequenceMgr.java
index 0bda10a..a61b378 100644
--- a/src/com/android/phone/SpecialCharSequenceMgr.java
+++ b/src/com/android/phone/SpecialCharSequenceMgr.java
@@ -50,7 +50,7 @@
* unify these two classes (in the framework? in a common shared library?)
*/
public class SpecialCharSequenceMgr {
- private static final String TAG = PhoneApp.LOG_TAG;
+ private static final String TAG = PhoneGlobals.LOG_TAG;
private static final boolean DBG = false;
private static final String MMI_IMEI_DISPLAY = "*#06#";
@@ -164,7 +164,7 @@
// input. We want to make sure that sim card contacts are NOT
// exposed unless the phone is unlocked, and this code can be
// accessed from the emergency dialer.
- if (PhoneApp.getInstance().getKeyguardManager().inKeyguardRestrictedInputMode()) {
+ if (PhoneGlobals.getInstance().getKeyguardManager().inKeyguardRestrictedInputMode()) {
return false;
}
@@ -178,7 +178,7 @@
"com.android.phone.SimContacts");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra("index", index);
- PhoneApp.getInstance().startActivity(intent);
+ PhoneGlobals.getInstance().startActivity(intent);
return true;
} catch (NumberFormatException ex) {}
@@ -193,7 +193,7 @@
// if a dialstring is an MMI code.
if ((input.startsWith("**04") || input.startsWith("**05"))
&& input.endsWith("#")) {
- PhoneApp app = PhoneApp.getInstance();
+ PhoneGlobals app = PhoneGlobals.getInstance();
boolean isMMIHandled = app.phone.handlePinMmi(input);
// if the PUK code is recognized then indicate to the
@@ -222,7 +222,7 @@
static private void showDeviceIdPanel(Context context) {
if (DBG) log("showDeviceIdPanel()...");
- Phone phone = PhoneApp.getPhone();
+ Phone phone = PhoneGlobals.getPhone();
int labelId = TelephonyCapabilities.getDeviceIdLabel(phone);
String deviceId = phone.getDeviceId();
diff --git a/src/com/android/phone/TimeConsumingPreferenceActivity.java b/src/com/android/phone/TimeConsumingPreferenceActivity.java
index 89275e7..19c4dda 100644
--- a/src/com/android/phone/TimeConsumingPreferenceActivity.java
+++ b/src/com/android/phone/TimeConsumingPreferenceActivity.java
@@ -24,7 +24,7 @@
implements TimeConsumingPreferenceListener,
DialogInterface.OnCancelListener {
private static final String LOG_TAG = "TimeConsumingPreferenceActivity";
- private final boolean DBG = (PhoneApp.DBG_LEVEL >= 2);
+ private final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
private class DismissOnClickListener implements DialogInterface.OnClickListener {
@Override
diff --git a/src/com/android/phone/Use2GOnlyCheckBoxPreference.java b/src/com/android/phone/Use2GOnlyCheckBoxPreference.java
index a25882e..4da238b 100644
--- a/src/com/android/phone/Use2GOnlyCheckBoxPreference.java
+++ b/src/com/android/phone/Use2GOnlyCheckBoxPreference.java
@@ -42,7 +42,7 @@
public Use2GOnlyCheckBoxPreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
- mPhone = PhoneApp.getPhone();
+ mPhone = PhoneGlobals.getPhone();
mHandler = new MyHandler();
mPhone.getPreferredNetworkType(
mHandler.obtainMessage(MyHandler.MESSAGE_GET_PREFERRED_NETWORK_TYPE));
@@ -54,8 +54,8 @@
int networkType = isChecked() ? Phone.NT_MODE_GSM_ONLY : Phone.NT_MODE_WCDMA_PREF;
Log.i(LOG_TAG, "set preferred network type="+networkType);
- android.provider.Settings.Secure.putInt(mPhone.getContext().getContentResolver(),
- android.provider.Settings.Secure.PREFERRED_NETWORK_MODE, networkType);
+ android.provider.Settings.Global.putInt(mPhone.getContext().getContentResolver(),
+ android.provider.Settings.Global.PREFERRED_NETWORK_MODE, networkType);
mPhone.setPreferredNetworkType(networkType, mHandler
.obtainMessage(MyHandler.MESSAGE_SET_PREFERRED_NETWORK_TYPE));
}
@@ -89,8 +89,8 @@
}
Log.i(LOG_TAG, "get preferred network type="+type);
setChecked(type == Phone.NT_MODE_GSM_ONLY);
- android.provider.Settings.Secure.putInt(mPhone.getContext().getContentResolver(),
- android.provider.Settings.Secure.PREFERRED_NETWORK_MODE, type);
+ android.provider.Settings.Global.putInt(mPhone.getContext().getContentResolver(),
+ android.provider.Settings.Global.PREFERRED_NETWORK_MODE, type);
} else {
// Weird state, disable the setting
Log.i(LOG_TAG, "get preferred network type, exception="+ar.exception);