summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJake Hamby <jhamby@google.com>2012-02-13 18:34:01 -0800
committerJake Hamby <jhamby@google.com>2012-03-13 16:48:21 -0700
commit00b87064abfb9d254fbbf72110643d2e626365e6 (patch)
treee5e540eec2dd2990d407bd7f2f6d443a619f58d2
parent95903862da74128bbab5c81d782c5ff277172498 (diff)
downloadCellBroadcastReceiver-00b87064abfb9d254fbbf72110643d2e626365e6.tar.gz
Add CMAS over CDMA support to CellBroadcastReceiver app.
Add support for CMAS warning notifications over CDMA to CellBroadcastReceiver app. The framework code now sends cell broadcasts in a radio-technology independent form by passing the new Parcelable android.telephony.SmsCbMessage class as the "message" intent of the SMS_CB_RECEIVED_ACTION or SMS_EMERGENCY_CB_RECEIVED_ACTION, instead of as a GSM/UMTS "pdu" byte array. The database version has also been upgraded to encompass metadata for ETWS and CMAS emergency broadcasts, as well as CDMA format broadcasts. Bug: 5856308 Change-Id: I1b10d35d8aa6f0134260116980e4c326f86d2fe7
-rw-r--r--Android.mk1
-rw-r--r--AndroidManifest.xml23
-rw-r--r--res/drawable-hdpi/alert_fullscreen_bg.9.pngbin0 -> 1414 bytes
-rw-r--r--res/drawable-mdpi/alert_fullscreen_bg.9.pngbin0 -> 882 bytes
-rw-r--r--res/drawable-xhdpi/alert_fullscreen_bg.9.pngbin0 -> 2159 bytes
-rw-r--r--res/layout-sw600dp/cell_broadcast_alert_fullscreen.xml34
-rw-r--r--res/layout/cell_broadcast_alert.xml112
-rw-r--r--res/layout/cell_broadcast_alert_fullscreen.xml34
-rw-r--r--res/layout/cell_broadcast_list_screen.xml30
-rw-r--r--res/values/dimens.xml27
-rw-r--r--res/values/strings.xml106
-rw-r--r--res/values/styles.xml25
-rw-r--r--res/xml/preferences.xml82
-rw-r--r--src/com/android/cellbroadcastreceiver/CellBroadcastAlertAudio.java16
-rw-r--r--src/com/android/cellbroadcastreceiver/CellBroadcastAlertDialog.java171
-rw-r--r--src/com/android/cellbroadcastreceiver/CellBroadcastAlertFullScreen.java205
-rw-r--r--src/com/android/cellbroadcastreceiver/CellBroadcastAlertService.java262
-rw-r--r--src/com/android/cellbroadcastreceiver/CellBroadcastAlertWakeLock.java52
-rw-r--r--src/com/android/cellbroadcastreceiver/CellBroadcastConfigService.java5
-rw-r--r--src/com/android/cellbroadcastreceiver/CellBroadcastCursorAdapter.java (renamed from src/com/android/cellbroadcastreceiver/CellBroadcastListAdapter.java)10
-rw-r--r--src/com/android/cellbroadcastreceiver/CellBroadcastCursorLoader.java181
-rw-r--r--src/com/android/cellbroadcastreceiver/CellBroadcastDatabase.java389
-rw-r--r--src/com/android/cellbroadcastreceiver/CellBroadcastDatabaseService.java76
-rw-r--r--src/com/android/cellbroadcastreceiver/CellBroadcastListActivity.java405
-rw-r--r--src/com/android/cellbroadcastreceiver/CellBroadcastListItem.java16
-rw-r--r--src/com/android/cellbroadcastreceiver/CellBroadcastMessage.java524
-rw-r--r--src/com/android/cellbroadcastreceiver/CellBroadcastReceiver.java87
-rw-r--r--src/com/android/cellbroadcastreceiver/CellBroadcastReceiverApp.java48
-rw-r--r--src/com/android/cellbroadcastreceiver/CellBroadcastSettings.java123
-rw-r--r--src/com/android/cellbroadcastreceiver/PrivilegedCellBroadcastReceiver.java1
-rw-r--r--tests/res/layout/test_buttons.xml55
-rw-r--r--tests/res/values/strings.xml6
-rw-r--r--tests/src/com/android/cellbroadcastreceiver/DialogSmsDisplayTests.java37
-rw-r--r--tests/src/com/android/cellbroadcastreceiver/tests/SendCdmaCmasMessages.java176
-rw-r--r--tests/src/com/android/cellbroadcastreceiver/tests/SendTestBroadcastActivity.java81
-rw-r--r--tests/src/com/android/cellbroadcastreceiver/tests/SendTestMessages.java112
36 files changed, 2640 insertions, 872 deletions
diff --git a/Android.mk b/Android.mk
index 5343d1aef..f439ec4ef 100644
--- a/Android.mk
+++ b/Android.mk
@@ -8,6 +8,7 @@ LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := CellBroadcastReceiver
+LOCAL_CERTIFICATE := platform
include $(BUILD_PACKAGE)
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 59234db81..e9c915872 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -54,12 +54,31 @@
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
+ <intent-filter>
+ <action android:name="android.cellbroadcastreceiver.UPDATE_LIST_VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
</activity>
<activity android:name="CellBroadcastSettings"
android:label="@string/sms_cb_settings"
android:exported="true" />
+ <activity android:name="CellBroadcastAlertDialog"
+ android:excludeFromRecents="true"
+ android:theme="@android:style/Theme.Holo.Dialog"
+ android:launchMode="singleInstance"
+ android:exported="false"
+ android:configChanges="orientation|keyboardHidden|keyboard|navigation" />
+
+ <!-- Full-screen version of CellBroadcastAlertDialog to display alerts over lock screen. -->
+ <activity android:name="CellBroadcastAlertFullScreen"
+ android:excludeFromRecents="true"
+ android:theme="@style/AlertFullScreenTheme"
+ android:launchMode="singleInstance"
+ android:exported="false"
+ android:configChanges="orientation|keyboardHidden|keyboard|navigation" />
+
<!-- Require sender permissions to prevent SMS spoofing -->
<receiver android:name="PrivilegedCellBroadcastReceiver"
android:permission="android.permission.BROADCAST_SMS">
@@ -70,6 +89,10 @@
<intent-filter>
<action android:name="android.provider.Telephony.SMS_CB_RECEIVED" />
</intent-filter>
+
+ <intent-filter>
+ <action android:name="android.provider.Telephony.SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED" />
+ </intent-filter>
</receiver>
<receiver android:name="CellBroadcastReceiver">
diff --git a/res/drawable-hdpi/alert_fullscreen_bg.9.png b/res/drawable-hdpi/alert_fullscreen_bg.9.png
new file mode 100644
index 000000000..79e56f522
--- /dev/null
+++ b/res/drawable-hdpi/alert_fullscreen_bg.9.png
Binary files differ
diff --git a/res/drawable-mdpi/alert_fullscreen_bg.9.png b/res/drawable-mdpi/alert_fullscreen_bg.9.png
new file mode 100644
index 000000000..fb3660eab
--- /dev/null
+++ b/res/drawable-mdpi/alert_fullscreen_bg.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/alert_fullscreen_bg.9.png b/res/drawable-xhdpi/alert_fullscreen_bg.9.png
new file mode 100644
index 000000000..f4970ad1c
--- /dev/null
+++ b/res/drawable-xhdpi/alert_fullscreen_bg.9.png
Binary files differ
diff --git a/res/layout-sw600dp/cell_broadcast_alert_fullscreen.xml b/res/layout-sw600dp/cell_broadcast_alert_fullscreen.xml
new file mode 100644
index 000000000..0430edacc
--- /dev/null
+++ b/res/layout-sw600dp/cell_broadcast_alert_fullscreen.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginLeft="10dp"
+ android:layout_marginRight="10dp" >
+
+ <FrameLayout
+ android:layout_width="400dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:background="@drawable/alert_fullscreen_bg" >
+ <include layout="@layout/cell_broadcast_alert"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </FrameLayout>
+
+</FrameLayout>
diff --git a/res/layout/cell_broadcast_alert.xml b/res/layout/cell_broadcast_alert.xml
new file mode 100644
index 000000000..85b129fb7
--- /dev/null
+++ b/res/layout/cell_broadcast_alert.xml
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/parentPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="8dip"
+ android:layout_marginRight="8dip"
+ android:orientation="vertical">
+
+ <LinearLayout android:id="@+id/topPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <View android:id="@+id/titleDividerTop"
+ android:layout_width="match_parent"
+ android:layout_height="2dip"
+ android:visibility="gone"
+ android:background="@android:color/holo_blue_light" />
+ <LinearLayout android:id="@+id/title_template"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:gravity="center_vertical|left"
+ android:minHeight="@dimen/alert_dialog_title_height"
+ android:layout_marginLeft="16dip"
+ android:layout_marginRight="16dip">
+ <ImageView android:id="@+id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="8dip"
+ android:src="@null" />
+ <com.android.internal.widget.DialogTitle android:id="@+id/alertTitle"
+ style="?android:attr/windowTitleStyle"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+ <View android:id="@+id/titleDivider"
+ android:layout_width="match_parent"
+ android:layout_height="2dip"
+ android:visibility="gone"
+ android:background="@android:color/holo_blue_light" />
+ </LinearLayout>
+
+ <LinearLayout android:id="@+id/contentPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical">
+ <ScrollView android:id="@+id/scrollView"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipToPadding="false">
+ <TextView android:id="@+id/message"
+ style="?android:attr/textAppearanceMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="16dip"
+ android:paddingRight="16dip"
+ android:paddingTop="8dip"
+ android:paddingBottom="8dip"/>
+ </ScrollView>
+ </LinearLayout>
+
+ <LinearLayout android:id="@+id/buttonPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/alert_dialog_button_bar_height"
+ android:orientation="vertical"
+ android:divider="?android:attr/dividerHorizontal"
+ android:showDividers="beginning"
+ android:dividerPadding="0dip">
+ <LinearLayout
+ style="?android:attr/buttonBarStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layoutDirection="locale"
+ android:measureWithLargestChild="true">
+ <Button android:id="@+id/dismissButton"
+ android:layout_width="0dip"
+ android:layout_gravity="center_horizontal"
+ android:layout_weight="1"
+ android:maxLines="2"
+ style="?android:attr/buttonBarButtonStyle"
+ android:text="@string/button_dismiss"
+ android:textSize="14sp"
+ android:minHeight="@dimen/alert_dialog_button_bar_height"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+ </LinearLayout>
+</LinearLayout>
diff --git a/res/layout/cell_broadcast_alert_fullscreen.xml b/res/layout/cell_broadcast_alert_fullscreen.xml
new file mode 100644
index 000000000..c73a495eb
--- /dev/null
+++ b/res/layout/cell_broadcast_alert_fullscreen.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginLeft="10dp"
+ android:layout_marginRight="10dp" >
+
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:background="@drawable/alert_fullscreen_bg" >
+ <include layout="@layout/cell_broadcast_alert"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </FrameLayout>
+
+</FrameLayout>
diff --git a/res/layout/cell_broadcast_list_screen.xml b/res/layout/cell_broadcast_list_screen.xml
index 44de8ee92..1e9631514 100644
--- a/res/layout/cell_broadcast_list_screen.xml
+++ b/res/layout/cell_broadcast_list_screen.xml
@@ -16,13 +16,25 @@
* limitations under the License.
*/
-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/listContainer"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
-<ListView android:id="@android:id/list" xmlns:android="http://schemas.android.com/apk/res/android"
- style="?android:attr/listViewWhiteStyle"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:drawSelectorOnTop="false"
- android:scrollbarStyle="insideOverlay"
- android:background="@android:color/white"
- android:cacheColorHint="@android:color/white"
- android:fadingEdgeLength="16dip" />
+ <ListView android:id="@android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:drawSelectorOnTop="false"
+ android:scrollbarStyle="insideOverlay"
+ android:background="@android:color/white"
+ android:cacheColorHint="@android:color/white"
+ android:fadingEdgeLength="16dip" />
+
+ <TextView android:id="@+android:id/empty"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:text="@string/no_cell_broadcasts"
+ android:textAppearance="?android:attr/textAppearanceLarge" />
+
+</FrameLayout>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
new file mode 100644
index 000000000..b87330761
--- /dev/null
+++ b/res/values/dimens.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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>
+
+ <!-- Dialog title height -->
+ <dimen name="alert_dialog_title_height">64dip</dimen>
+ <!-- Dialog button bar height -->
+ <dimen name="alert_dialog_button_bar_height">48dip</dimen>
+
+</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index e28365ae0..420bf91c0 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -26,10 +26,16 @@
<!-- Text for dismiss button in broadcast message view dialog. [CHAR LIMIT=25] -->
<string name="button_dismiss">OK</string>
+ <!-- Text for list view when empty (no broadcasts). [CHAR LIMIT=200] -->
+ <string name="no_cell_broadcasts">There are no active alerts in your area. You can change the Alert settings using the Settings menu option.</string>
+
<!-- Menu item for accessing application settings. [CHAR LIMIT=30] -->
<string name="menu_preferences">Settings</string>
<!-- Menu item for deleting all broadcasts. [CHAR LIMIT=30] -->
<string name="menu_delete_all">Delete broadcasts</string>
+
+ <!-- Header for context menu on an individual broadcast. [CHAR LIMIT=30] -->
+ <string name="message_options">Message options</string>
<!-- Context menu item to view a previously received broadcast. [CHAR LIMIT=30] -->
<string name="menu_view">View broadcast</string>
<!-- Context menu item to delete a previously received broadcast. [CHAR LIMIT=30] -->
@@ -94,24 +100,27 @@
<!-- Preference category title for ETWS settings. [CHAR LIMIT=50] -->
<string name="category_etws_settings_title">ETWS settings</string>
<!-- Preference title for enable ETWS test alerts checkbox. [CHAR LIMIT=30] -->
- <string name="enable_etws_test_alerts_title">Show test broadcasts</string>
+ <string name="enable_etws_test_alerts_title">Show ETWS test broadcasts</string>
<!-- Preference summary for enable ETWS test alerts checkbox. [CHAR LIMIT=100] -->
<string name="enable_etws_test_alerts_summary">Display test broadcasts for Earthquake Tsunami Warning System</string>
- <!-- Preference category title for CMAS settings. [CHAR LIMIT=50] -->
- <string name="category_cmas_settings_title">CMAS settings</string>
- <!-- Preference title for enable CMAS imminent threat alerts checkbox. [CHAR LIMIT=30] -->
- <string name="enable_cmas_imminent_threat_alerts_title">Show imminent threats</string>
- <!-- Preference summary for enable CMAS imminent threat alerts checkbox. [CHAR LIMIT=100] -->
- <string name="enable_cmas_imminent_threat_alerts_summary">Display CMAS imminent threat warning notifications</string>
+ <!-- Preference title for enable CMAS extreme threat alerts checkbox. [CHAR LIMIT=30] -->
+ <string name="enable_cmas_extreme_threat_alerts_title">Show extreme threats</string>
+ <!-- Preference summary for enable CMAS extreme threat alerts checkbox. [CHAR LIMIT=100] -->
+ <string name="enable_cmas_extreme_threat_alerts_summary">Display alerts for extreme threats to life and property</string>
+ <!-- Preference title for enable CMAS severe threat alerts checkbox. [CHAR LIMIT=30] -->
+ <string name="enable_cmas_severe_threat_alerts_title">Show severe threats</string>
+ <!-- Preference summary for enable CMAS severe threat alerts checkbox. [CHAR LIMIT=100] -->
+ <string name="enable_cmas_severe_threat_alerts_summary">Display alerts for severe threats to life and property</string>
<!-- Preference title for enable CMAS amber alerts checkbox. [CHAR LIMIT=50] -->
- <string name="enable_cmas_amber_alerts_title">Show Amber alerts</string>
+ <string name="enable_cmas_amber_alerts_title">Show AMBER alerts</string>
<!-- Preference summary for enable CMAS amber alerts checkbox. [CHAR LIMIT=100] -->
- <string name="enable_cmas_amber_alerts_summary">Display Amber alert (child abduction emergency) bulletins</string>
+ <string name="enable_cmas_amber_alerts_summary">Display child abduction emergency bulletins (AMBER alert)</string>
+
<!-- Preference title for enable CMAS test alerts checkbox. [CHAR LIMIT=30] -->
- <string name="enable_cmas_test_alerts_title">Show test broadcasts</string>
+ <string name="enable_cmas_test_alerts_title">Show CMAS test broadcasts</string>
<!-- Preference summary for enable CMAS test alerts checkbox. [CHAR LIMIT=100] -->
- <string name="enable_cmas_test_alerts_summary">Display CMAS monthly test broadcasts</string>
+ <string name="enable_cmas_test_alerts_summary">Display test broadcasts for Commercial Mobile Alert System</string>
<!-- Preference category title for Brazil settings. [CHAR LIMIT=50] -->
<string name="category_brazil_settings_title">Settings for Brazil</string>
@@ -120,6 +129,81 @@
<!-- Preference summary for enable channel 50 alerts (Brazil only). [CHAR LIMIT=100] -->
<string name="enable_channel_50_alerts_summary">Channel 50 is used in Brazil for area update information</string>
+ <!-- Preference category title for developer settings. [CHAR LIMIT=50] -->
+ <string name="category_dev_settings_title">Developer options</string>
+
+ <!-- CMAS alert category heading (including colon and space). [CHAR LIMIT=30] -->
+ <string name="cmas_category_heading">Category: </string>
+ <!-- CMAS category for geophysical alerts. [CHAR LIMIT=50] -->
+ <string name="cmas_category_geo">Geophysical</string>
+ <!-- CMAS category for meteorological alerts. [CHAR LIMIT=50] -->
+ <string name="cmas_category_met">Meteorological</string>
+ <!-- CMAS category for general emergency and public safety alerts. [CHAR LIMIT=50] -->
+ <string name="cmas_category_safety">Safety</string>
+ <!-- CMAS category for security alerts (law enforcement, military, etc.). [CHAR LIMIT=50] -->
+ <string name="cmas_category_security">Security</string>
+ <!-- CMAS category for rescue and recovery alerts. [CHAR LIMIT=50] -->
+ <string name="cmas_category_rescue">Rescue</string>
+ <!-- CMAS category for fire suppression and rescue alerts. [CHAR LIMIT=50] -->
+ <string name="cmas_category_fire">Fire</string>
+ <!-- CMAS category for medical and public health alerts. [CHAR LIMIT=50] -->
+ <string name="cmas_category_health">Health</string>
+ <!-- CMAS category for pollution and other environmental alerts. [CHAR LIMIT=50] -->
+ <string name="cmas_category_env">Environmental</string>
+ <!-- CMAS category for transportation alerts. [CHAR LIMIT=50] -->
+ <string name="cmas_category_transport">Transportation</string>
+ <!-- CMAS category for utility, telecommunication, and other infrastructure alerts. [CHAR LIMIT=50] -->
+ <string name="cmas_category_infra">Infrastructure</string>
+ <!-- CMAS category for chemical, biological, radiological, nuclear alerts. [CHAR LIMIT=50] -->
+ <string name="cmas_category_cbrne">Chemical/Biological/Nuclear/Explosive</string>
+ <!-- CMAS category for other alerts. [CHAR LIMIT=50] -->
+ <string name="cmas_category_other">Other</string>
+
+ <!-- CMAS response type heading (including colon and space). [CHAR LIMIT=30] -->
+ <string name="cmas_response_heading">Response type: </string>
+ <!-- CMAS response type: take shelter in place. [CHAR LIMIT=50] -->
+ <string name="cmas_response_shelter">Take shelter</string>
+ <!-- CMAS response type: evacuate (relocate). [CHAR LIMIT=50] -->
+ <string name="cmas_response_evacuate">Evacuate</string>
+ <!-- CMAS response type: make preparations. [CHAR LIMIT=50] -->
+ <string name="cmas_response_prepare">Prepare</string>
+ <!-- CMAS response type: execute a pre-planned activity. [CHAR LIMIT=50] -->
+ <string name="cmas_response_execute">Execute activity</string>
+ <!-- CMAS response type: monitor information sources. [CHAR LIMIT=50] -->
+ <string name="cmas_response_monitor">Monitor info sources</string>
+ <!-- CMAS response type: avoid hazard. [CHAR LIMIT=50] -->
+ <string name="cmas_response_avoid">Avoid hazard</string>
+ <!-- CMAS response type: evaluate the information in this message. [CHAR LIMIT=50] -->
+ <string name="cmas_response_assess">Evaluate</string>
+ <!-- CMAS response type: no action recommended. [CHAR LIMIT=50] -->
+ <string name="cmas_response_none">No action</string>
+
+ <!-- CMAS severity heading (including colon and space). [CHAR LIMIT=30] -->
+ <string name="cmas_severity_heading">Severity: </string>
+ <!-- CMAS severity type: extreme. [CHAR LIMIT=30] -->
+ <string name="cmas_severity_extreme">Extreme</string>
+ <!-- CMAS severity type: severe. [CHAR LIMIT=30] -->
+ <string name="cmas_severity_severe">Severe</string>
+
+ <!-- CMAS urgency heading (including colon and space). [CHAR LIMIT=30] -->
+ <string name="cmas_urgency_heading">Urgency: </string>
+ <!-- CMAS urgency type: take responsive action immediately. [CHAR LIMIT=30] -->
+ <string name="cmas_urgency_immediate">Take action immediately</string>
+ <!-- CMAS severity type: severe. [CHAR LIMIT=30] -->
+ <string name="cmas_urgency_expected">Take action within the next hour</string>
+
+ <!-- CMAS certainty heading (including colon and space). [CHAR LIMIT=30] -->
+ <string name="cmas_certainty_heading">Certainty: </string>
+ <!-- CMAS certainty type: observed. [CHAR LIMIT=30] -->
+ <string name="cmas_certainty_observed">Observed</string>
+ <!-- CMAS severity type: severe. [CHAR LIMIT=30] -->
+ <string name="cmas_certainty_likely">Likely</string>
+
+ <!-- Non-emergency broadcast notification description for multiple unread alerts. -->
+ <string name="notification_multiple"><xliff:g id="count">%s</xliff:g> unread alerts.</string>
+ <!-- Non-emergency broadcast notification title for multiple unread alerts. -->
+ <string name="notification_multiple_title">New alerts</string>
+
<!-- Entries listed in the ListPreference for allowed alert durations. [CHAR LIMIT=30] -->
<string-array name="alert_sound_duration_entries">
<item>2 seconds</item>
diff --git a/res/values/styles.xml b/res/values/styles.xml
new file mode 100644
index 000000000..2cdbd2db3
--- /dev/null
+++ b/res/values/styles.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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>
+
+ <!-- This must be a fullscreen theme for the alarm to be able to turn the screen on. -->
+ <style name="AlertFullScreenTheme" parent="android:Theme.Holo.Wallpaper.NoTitleBar" />
+
+</resources>
diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml
index e1a5bc9c8..164bbd0d5 100644
--- a/res/xml/preferences.xml
+++ b/res/xml/preferences.xml
@@ -18,55 +18,30 @@
-->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Emergency alert settings for CMAS. -->
<PreferenceCategory android:title="@string/emergency_alert_settings_title"
- android:key="emergency_alert_settings">
+ android:key="category_cmas_settings">
+ <!-- Enable CMAS Extreme Threat alerts -->
<CheckBoxPreference android:defaultValue="true"
- android:key="enable_emergency_alerts"
- android:summary="@string/enable_emergency_alerts_summary"
- android:title="@string/enable_emergency_alerts_title" />
-
- <ListPreference android:key="alert_sound_duration"
- android:title="@string/alert_sound_duration_title"
- android:entries="@array/alert_sound_duration_entries"
- android:entryValues="@array/alert_sound_duration_values"
- android:defaultValue="4"
- android:dialogTitle="@string/alert_sound_duration_title" />
+ android:key="enable_cmas_extreme_threat_alerts"
+ android:summary="@string/enable_cmas_extreme_threat_alerts_summary"
+ android:title="@string/enable_cmas_extreme_threat_alerts_title" />
+ <!-- Enable CMAS Severe Threat alerts -->
<CheckBoxPreference android:defaultValue="true"
- android:key="enable_alert_speech"
- android:summary="@string/enable_alert_speech_summary"
- android:title="@string/enable_alert_speech_title" />
-
- </PreferenceCategory>
- <PreferenceCategory android:title="@string/category_etws_settings_title"
- android:key="category_etws_settings">
-
- <CheckBoxPreference android:defaultValue="false"
- android:key="enable_etws_test_alerts"
- android:summary="@string/enable_etws_test_alerts_summary"
- android:title="@string/enable_etws_test_alerts_title" />
-
- </PreferenceCategory>
- <PreferenceCategory android:title="@string/category_cmas_settings_title"
- android:key="category_cmas_settings">
+ android:key="enable_cmas_severe_threat_alerts"
+ android:summary="@string/enable_cmas_severe_threat_alerts_summary"
+ android:title="@string/enable_cmas_severe_threat_alerts_title" />
+ <!-- Enable CMAS AMBER alerts -->
<CheckBoxPreference android:defaultValue="true"
- android:key="enable_cmas_imminent_threat_alerts"
- android:summary="@string/enable_cmas_imminent_threat_alerts_summary"
- android:title="@string/enable_cmas_imminent_threat_alerts_title" />
-
- <CheckBoxPreference android:defaultValue="false"
android:key="enable_cmas_amber_alerts"
android:summary="@string/enable_cmas_amber_alerts_summary"
android:title="@string/enable_cmas_amber_alerts_title" />
- <CheckBoxPreference android:defaultValue="false"
- android:key="enable_cmas_test_alerts"
- android:summary="@string/enable_cmas_test_alerts_summary"
- android:title="@string/enable_cmas_test_alerts_title" />
-
</PreferenceCategory>
+
<PreferenceCategory android:title="@string/category_brazil_settings_title"
android:key="category_brazil_settings">
@@ -79,4 +54,37 @@
</PreferenceCategory>
+ <!-- Only visible when Developer options toggle is enabled in Settings. -->
+ <PreferenceCategory android:title="@string/category_dev_settings_title"
+ android:key="category_dev_settings">
+
+ <CheckBoxPreference android:defaultValue="false"
+ android:key="enable_etws_test_alerts"
+ android:summary="@string/enable_etws_test_alerts_summary"
+ android:title="@string/enable_etws_test_alerts_title" />
+
+ <CheckBoxPreference android:defaultValue="false"
+ android:key="enable_cmas_test_alerts"
+ android:summary="@string/enable_cmas_test_alerts_summary"
+ android:title="@string/enable_cmas_test_alerts_title" />
+
+ <CheckBoxPreference android:defaultValue="true"
+ android:key="enable_emergency_alerts"
+ android:summary="@string/enable_emergency_alerts_summary"
+ android:title="@string/enable_emergency_alerts_title" />
+
+ <ListPreference android:key="alert_sound_duration"
+ android:title="@string/alert_sound_duration_title"
+ android:entries="@array/alert_sound_duration_entries"
+ android:entryValues="@array/alert_sound_duration_values"
+ android:defaultValue="4"
+ android:dialogTitle="@string/alert_sound_duration_title" />
+
+ <CheckBoxPreference android:defaultValue="true"
+ android:key="enable_alert_speech"
+ android:summary="@string/enable_alert_speech_summary"
+ android:title="@string/enable_alert_speech_title" />
+
+ </PreferenceCategory>
+
</PreferenceScreen>
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertAudio.java b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertAudio.java
index 1ae70c133..ac5055d41 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertAudio.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertAudio.java
@@ -27,6 +27,7 @@ import android.media.MediaPlayer.OnErrorListener;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
+import android.os.PowerManager;
import android.os.Vibrator;
import android.speech.tts.TextToSpeech;
import android.telephony.PhoneStateListener;
@@ -66,6 +67,9 @@ public class CellBroadcastAlertAudio extends Service implements TextToSpeech.OnI
/** Vibration uses the same on/off pattern as the CMAS alert tone */
private static final long[] sVibratePattern = new long[] { 0, 2000, 500, 1000, 500, 1000, 500 };
+ /** CPU wake lock while playing audio. */
+ private PowerManager.WakeLock mWakeLock;
+
private static final int STATE_IDLE = 0;
private static final int STATE_ALERTING = 1;
private static final int STATE_PAUSING = 2;
@@ -141,6 +145,7 @@ public class CellBroadcastAlertAudio extends Service implements TextToSpeech.OnI
* Callback from TTS engine after initialization.
* @param status {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
*/
+ @Override
public void onInit(int status) {
if (DBG) Log.v(TAG, "onInit() TTS engine status: " + status);
if (status == TextToSpeech.SUCCESS) {
@@ -176,12 +181,18 @@ public class CellBroadcastAlertAudio extends Service implements TextToSpeech.OnI
* Callback from TTS engine.
* @param utteranceId the identifier of the utterance.
*/
+ @Override
public void onUtteranceCompleted(String utteranceId) {
stopSelf();
}
@Override
public void onCreate() {
+ // acquire CPU wake lock while playing audio
+ PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+ mWakeLock.acquire();
+
mVibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
// Listen for incoming calls to kill the alarm.
@@ -189,7 +200,6 @@ public class CellBroadcastAlertAudio extends Service implements TextToSpeech.OnI
(TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
mTelephonyManager.listen(
mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
- CellBroadcastAlertWakeLock.acquireCpuWakeLock(this);
}
@Override
@@ -197,12 +207,13 @@ public class CellBroadcastAlertAudio extends Service implements TextToSpeech.OnI
stop();
// Stop listening for incoming calls.
mTelephonyManager.listen(mPhoneStateListener, 0);
- CellBroadcastAlertWakeLock.releaseCpuLock();
// shutdown TTS engine
if (mTts != null) {
mTts.stop();
mTts.shutdown();
}
+ // release CPU wake lock
+ mWakeLock.release();
}
@Override
@@ -276,6 +287,7 @@ public class CellBroadcastAlertAudio extends Service implements TextToSpeech.OnI
Log.v(TAG, "in call: reducing volume");
mMediaPlayer.setVolume(IN_CALL_VOLUME, IN_CALL_VOLUME);
}
+ // start playing alert audio
setDataSourceFromResource(getResources(), mMediaPlayer,
R.raw.attention_signal);
mAudioManager.requestAudioFocus(null, AudioManager.STREAM_ALARM,
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertDialog.java b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertDialog.java
index eed3d20a7..e762678a1 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertDialog.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertDialog.java
@@ -16,168 +16,71 @@
package com.android.cellbroadcastreceiver;
-import android.app.AlertDialog;
import android.app.KeyguardManager;
+import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.Intent;
-import android.graphics.drawable.Drawable;
+import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
-import android.view.WindowManager;
-import android.widget.ImageView;
+import android.util.Log;
/**
* Custom alert dialog with optional flashing warning icon.
* Alert audio and text-to-speech handled by {@link CellBroadcastAlertAudio}.
+ * Keyguard handling based on {@code AlarmAlert} class from DeskClock app.
*/
-public class CellBroadcastAlertDialog extends AlertDialog {
+public class CellBroadcastAlertDialog extends CellBroadcastAlertFullScreen {
+ private static final String TAG = "CellBroadcastAlertDialog";
- /** Whether to show the flashing warning icon. */
- private final boolean mShowWarningIcon;
+ private BroadcastReceiver mScreenOffReceiver;
- /** The broadcast delivery time, for marking as read (or 0). */
- private final long mDeliveryTime;
-
- /** Length of time for the warning icon to be visible. */
- private static final int WARNING_ICON_ON_DURATION_MSEC = 800;
-
- /** Length of time for the warning icon to be off. */
- private static final int WARNING_ICON_OFF_DURATION_MSEC = 800;
-
- /** Warning icon state. false = visible, true = off */
- private boolean mIconAnimationState;
-
- /** Stop animating icon after {@link #onStop()} is called. */
- private boolean mStopAnimation;
-
- /** The warning icon Drawable. */
- private Drawable mWarningIcon;
-
- /** The View containing the warning icon. */
- private ImageView mWarningIconView;
-
- /** Keyguard lock to show emergency alerts while in the lock screen. */
- private KeyguardManager.KeyguardLock mKeyguardLock;
-
- /** Icon animation handler for flashing warning alerts. */
- private final Handler mAnimationHandler = new Handler() {
+ private class ScreenOffReceiver extends BroadcastReceiver {
@Override
- public void handleMessage(Message msg) {
- if (mIconAnimationState) {
- mWarningIconView.setAlpha(255);
- if (!mStopAnimation) {
- mAnimationHandler.sendEmptyMessageDelayed(0, WARNING_ICON_ON_DURATION_MSEC);
- }
- } else {
- mWarningIconView.setAlpha(0);
- if (!mStopAnimation) {
- mAnimationHandler.sendEmptyMessageDelayed(0, WARNING_ICON_OFF_DURATION_MSEC);
- }
- }
- mIconAnimationState = !mIconAnimationState;
- mWarningIconView.invalidateDrawable(mWarningIcon);
- }
- };
-
- /**
- * Create a new alert dialog for the broadcast notification.
- * @param context the local Context
- * @param titleId the resource ID of the dialog title
- * @param body the message body contents
- * @param showWarningIcon true if the flashing warning icon should be shown
- * @param deliveryTime the delivery time of the broadcast, for marking as read
- */
- public CellBroadcastAlertDialog(Context context, int titleId, CharSequence body,
- boolean showWarningIcon, long deliveryTime) {
- super(context);
- mShowWarningIcon = showWarningIcon;
- mDeliveryTime = deliveryTime;
-
- setTitle(titleId);
- setMessage(body);
- setCancelable(true);
- setOnCancelListener(new AlertDialog.OnCancelListener() {
- public void onCancel(DialogInterface dialog) {
- dialog.dismiss();
- }
- });
- setButton(DialogInterface.BUTTON_NEUTRAL, context.getText(R.string.button_dismiss),
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int whichButton) {
- dialog.dismiss();
- }
- });
-
- // Set warning icon for emergency alert
- if (mShowWarningIcon) {
- mWarningIcon = getContext().getResources().getDrawable(R.drawable.ic_warning_large);
- setIcon(mWarningIcon);
+ public void onReceive(Context context, Intent intent) {
+ handleScreenOff();
}
}
@Override
- protected void onCreate(Bundle savedInstanceState) {
- if (mShowWarningIcon) {
- // Turn screen on and show above the keyguard for emergency alert
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
- | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
- | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
- | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
- | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON);
- }
- super.onCreate(savedInstanceState);
- if (mShowWarningIcon) {
- KeyguardManager km = (KeyguardManager)
- getContext().getSystemService(Context.KEYGUARD_SERVICE);
- mKeyguardLock = km.newKeyguardLock("CellBroadcastReceiver");
- mWarningIconView = (ImageView) findViewById(com.android.internal.R.id.icon);
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ // Listen for the screen turning off so that when the screen comes back
+ // on, the user does not need to unlock the phone to dismiss the alert.
+ if (mMessage.isEmergencyAlertMessage()) {
+ mScreenOffReceiver = new ScreenOffReceiver();
+ registerReceiver(mScreenOffReceiver,
+ new IntentFilter(Intent.ACTION_SCREEN_OFF));
}
}
- /**
- * Start animating warning icon.
- */
@Override
- protected void onStart() {
- if (mShowWarningIcon) {
- // Disable keyguard
- mKeyguardLock.disableKeyguard();
- // start icon animation
- mAnimationHandler.sendEmptyMessageDelayed(0, WARNING_ICON_ON_DURATION_MSEC);
+ public void onDestroy() {
+ super.onDestroy();
+ if (mScreenOffReceiver != null) {
+ unregisterReceiver(mScreenOffReceiver);
}
}
- /**
- * Stop animating warning icon and stop the {@link CellBroadcastAlertAudio}
- * service if necessary.
- */
@Override
- protected void onStop() {
- // Stop playing alert sound/vibration/speech (if started)
- Context context = getContext();
- context.stopService(new Intent(context, CellBroadcastAlertAudio.class));
- // Start database service to mark broadcast as read
- Intent intent = new Intent(context, CellBroadcastDatabaseService.class);
- intent.setAction(CellBroadcastDatabaseService.ACTION_MARK_BROADCAST_READ);
- intent.putExtra(CellBroadcastDatabaseService.DATABASE_DELIVERY_TIME_EXTRA, mDeliveryTime);
- context.startService(intent);
- if (mShowWarningIcon) {
- // Reenable keyguard
- mKeyguardLock.reenableKeyguard();
- // stop animating icon
- mStopAnimation = true;
- }
+ public void onBackPressed() {
+ // stop animating warning icon, stop playing alert sound, mark broadcast as read
+ dismiss();
}
- /**
- * Ignore the back button for emergency alerts (user must dismiss with button).
- */
@Override
- public void onBackPressed() {
- if (!mShowWarningIcon) {
- super.onBackPressed();
- }
+ protected int getLayoutResId() {
+ return R.layout.cell_broadcast_alert;
+ }
+
+ private void handleScreenOff() {
+ // Launch the full screen activity but do not turn the screen on.
+ Intent i = new Intent(this, CellBroadcastAlertFullScreen.class);
+ i.putExtra(CellBroadcastMessage.SMS_CB_MESSAGE_EXTRA, mMessage);
+ i.putExtra(SCREEN_OFF, true);
+ startActivity(i);
+ finish();
}
}
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertFullScreen.java b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertFullScreen.java
new file mode 100644
index 000000000..ea6c5fcb7
--- /dev/null
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertFullScreen.java
@@ -0,0 +1,205 @@
+/*
+ * 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.cellbroadcastreceiver;
+
+import android.app.Activity;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ * Full-screen emergency alert with flashing warning icon.
+ * Alert audio and text-to-speech handled by {@link CellBroadcastAlertAudio}.
+ * Keyguard handling based on {@code AlarmAlertFullScreen} class from DeskClock app.
+ */
+public class CellBroadcastAlertFullScreen extends Activity {
+
+ /**
+ * Intent extra for full screen alert launched from dialog subclass as a result of the
+ * screen turning off.
+ */
+ static final String SCREEN_OFF = "screen_off";
+
+ /** Whether to show the flashing warning icon. */
+ private boolean mIsEmergencyAlert;
+
+ /** The cell broadcast message to display. */
+ CellBroadcastMessage mMessage;
+
+ /** Length of time for the warning icon to be visible. */
+ private static final int WARNING_ICON_ON_DURATION_MSEC = 800;
+
+ /** Length of time for the warning icon to be off. */
+ private static final int WARNING_ICON_OFF_DURATION_MSEC = 800;
+
+ /** Warning icon state. false = visible, true = off */
+ private boolean mIconAnimationState;
+
+ /** Stop animating icon after {@link #onStop()} is called. */
+ private boolean mStopAnimation;
+
+ /** The warning icon Drawable. */
+ private Drawable mWarningIcon;
+
+ /** The View containing the warning icon. */
+ private ImageView mWarningIconView;
+
+ /** Icon animation handler for flashing warning alerts. */
+ private final Handler mAnimationHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (mIconAnimationState) {
+ mWarningIconView.setAlpha(255);
+ if (!mStopAnimation) {
+ mAnimationHandler.sendEmptyMessageDelayed(0, WARNING_ICON_ON_DURATION_MSEC);
+ }
+ } else {
+ mWarningIconView.setAlpha(0);
+ if (!mStopAnimation) {
+ mAnimationHandler.sendEmptyMessageDelayed(0, WARNING_ICON_OFF_DURATION_MSEC);
+ }
+ }
+ mIconAnimationState = !mIconAnimationState;
+ mWarningIconView.invalidateDrawable(mWarningIcon);
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final Window win = getWindow();
+
+ // We use a custom title, so remove the standard dialog title bar
+ win.requestFeature(Window.FEATURE_NO_TITLE);
+
+ // Full screen alerts display above the keyguard and when device is locked.
+ win.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
+
+ // Turn on the screen unless we're being launched from the dialog subclass as a result of
+ // the screen turning off.
+ if (!getIntent().getBooleanExtra(SCREEN_OFF, false)) {
+ win.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+
+ // Save message for passing from dialog to fullscreen activity, and for marking read.
+ mMessage = getIntent().getParcelableExtra(
+ CellBroadcastMessage.SMS_CB_MESSAGE_EXTRA);
+
+ updateLayout(mMessage);
+ }
+
+ protected int getLayoutResId() {
+ return R.layout.cell_broadcast_alert_fullscreen;
+ }
+
+ private void updateLayout(CellBroadcastMessage message) {
+ LayoutInflater inflater = LayoutInflater.from(this);
+
+ setContentView(inflater.inflate(getLayoutResId(), null));
+
+ /* Initialize dialog text from alert message. */
+ int titleId = message.getDialogTitleResource();
+ setTitle(titleId);
+ ((TextView) findViewById(R.id.alertTitle)).setText(titleId);
+ ((TextView) findViewById(R.id.message)).setText(message.getFormattedMessageBody(this));
+
+ /* dismiss button: close notification */
+ findViewById(R.id.dismissButton).setOnClickListener(
+ new Button.OnClickListener() {
+ public void onClick(View v) {
+ dismiss();
+ }
+ });
+
+ mIsEmergencyAlert = message.isPublicAlertMessage() || CellBroadcastConfigService
+ .isOperatorDefinedEmergencyId(message.getServiceCategory());
+
+ if (mIsEmergencyAlert) {
+ mWarningIcon = getResources().getDrawable(R.drawable.ic_warning_large);
+ mWarningIconView = (ImageView) findViewById(R.id.icon);
+ if (mWarningIconView != null) {
+ mWarningIconView.setImageDrawable(mWarningIcon);
+ }
+
+ // Dismiss the notification that brought us here
+ ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE))
+ .cancel((int) message.getDeliveryTime());
+ }
+ }
+
+ /**
+ * Start animating warning icon.
+ */
+ @Override
+ protected void onStart() {
+ super.onStart();
+ if (mIsEmergencyAlert) {
+ // start icon animation
+ mAnimationHandler.sendEmptyMessageDelayed(0, WARNING_ICON_ON_DURATION_MSEC);
+ }
+ }
+
+ /**
+ * Stop animating warning icon and stop the {@link CellBroadcastAlertAudio}
+ * service if necessary.
+ */
+ void dismiss() {
+ // Stop playing alert sound/vibration/speech (if started)
+ stopService(new Intent(this, CellBroadcastAlertAudio.class));
+
+ // Start database service to mark broadcast as read
+ Intent intent = new Intent(this, CellBroadcastDatabaseService.class);
+ intent.setAction(CellBroadcastDatabaseService.ACTION_MARK_BROADCAST_READ);
+ // Select by delivery time because we don't know the database row ID.
+ intent.putExtra(CellBroadcastDatabaseService.DATABASE_DELIVERY_TIME_EXTRA,
+ mMessage.getDeliveryTime());
+ startService(intent);
+
+ if (mIsEmergencyAlert) {
+ // stop animating emergency alert icon
+ mStopAnimation = true;
+ } else {
+ // decrement unread non-emergency alert count
+ CellBroadcastReceiverApp.decrementUnreadAlertCount();
+ }
+ finish();
+ }
+
+ /**
+ * Ignore the back button for emergency alerts (overridden by alert dialog so that the dialog
+ * is dismissed).
+ */
+ @Override
+ public void onBackPressed() {
+ // ignored
+ }
+}
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertService.java b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertService.java
index dafb3917a..a81eb32ca 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertService.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertService.java
@@ -16,6 +16,7 @@
package com.android.cellbroadcastreceiver;
+import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -25,9 +26,10 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.PowerManager;
import android.preference.PreferenceManager;
import android.provider.Telephony;
-import android.telephony.SmsCbConstants;
+import android.telephony.SmsCbCmasInfo;
import android.telephony.SmsCbMessage;
import android.util.Log;
@@ -44,6 +46,18 @@ public class CellBroadcastAlertService extends Service {
public static final String SMS_CB_NOTIFICATION_ID_EXTRA =
"com.android.cellbroadcastreceiver.SMS_CB_NOTIFICATION_ID";
+ /** Intent extra to indicate a previously unread alert. */
+ static final String NEW_ALERT_EXTRA = "com.android.cellbroadcastreceiver.NEW_ALERT";
+
+ /** Use the same notification ID for non-emergency alerts. */
+ static final int NOTIFICATION_ID = 1;
+
+ /** CPU wake lock while handling emergency alert notification. */
+ private PowerManager.WakeLock mWakeLock;
+
+ /** Hold the wake lock for 5 seconds, which should be enough time to display the alert. */
+ private static final int WAKE_LOCK_TIMEOUT = 5000;
+
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String action = intent.getAction();
@@ -64,68 +78,29 @@ public class CellBroadcastAlertService extends Service {
return;
}
- Object[] pdus = (Object[]) extras.get("pdus");
-
- if (pdus == null || pdus.length < 1) {
- Log.e(TAG, "received SMS_CB_RECEIVED_ACTION with no pdus");
- return;
- }
+ SmsCbMessage message = (SmsCbMessage) extras.get("message");
- // create message from first PDU
- SmsCbMessage message = SmsCbMessage.createFromPdu((byte[]) pdus[0]);
if (message == null) {
- Log.e(TAG, "failed to create SmsCbMessage from PDU: " + pdus[0]);
+ Log.e(TAG, "received SMS_CB_RECEIVED_ACTION with no message extra");
return;
}
- // append message bodies from any additional PDUs (GSM only)
- for (int i = 1; i < pdus.length; i++) {
- SmsCbMessage nextPage = SmsCbMessage.createFromPdu((byte[]) pdus[i]);
- if (nextPage != null) {
- message.appendToBody(nextPage.getMessageBody());
- } else {
- Log.w(TAG, "failed to append to SmsCbMessage from PDU: " + pdus[i]);
- // continue so we can show the first page of the broadcast
- }
- }
-
final CellBroadcastMessage cbm = new CellBroadcastMessage(message);
if (!isMessageEnabledByUser(cbm)) {
- Log.d(TAG, "ignoring alert of type " + cbm.getMessageIdentifier() +
+ Log.d(TAG, "ignoring alert of type " + cbm.getServiceCategory() +
" by user preference");
return;
}
- // add notification to the bar
- addToNotificationBar(cbm);
if (cbm.isEmergencyAlertMessage() || CellBroadcastConfigService
- .isOperatorDefinedEmergencyId(cbm.getMessageIdentifier())) {
- // start audio/vibration/speech service for emergency alerts
- Intent audioIntent = new Intent(this, CellBroadcastAlertAudio.class);
- audioIntent.setAction(CellBroadcastAlertAudio.ACTION_START_ALERT_AUDIO);
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
- String duration = prefs.getString(CellBroadcastSettings.KEY_ALERT_SOUND_DURATION,
- CellBroadcastSettings.ALERT_SOUND_DEFAULT_DURATION);
- audioIntent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_DURATION_EXTRA,
- Integer.parseInt(duration));
-
- if (prefs.getBoolean(CellBroadcastSettings.KEY_ENABLE_ALERT_SPEECH, true)) {
- audioIntent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_MESSAGE_BODY,
- cbm.getMessageBody());
-
- String language = cbm.getLanguageCode();
- if (cbm.isEtwsMessage() && !"ja".equals(language)) {
- Log.w(TAG, "bad language code for ETWS - using Japanese TTS");
- language = "ja";
- } else if (cbm.isCmasMessage() && !"en".equals(language)) {
- Log.w(TAG, "bad language code for CMAS - using English TTS");
- language = "en";
- }
- audioIntent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_MESSAGE_LANGUAGE,
- language);
- }
- startService(audioIntent);
+ .isOperatorDefinedEmergencyId(cbm.getServiceCategory())) {
+ // start alert sound / vibration / TTS and display full-screen alert
+ openEmergencyAlertNotification(cbm);
+ } else {
+ // add notification to the bar
+ addToNotificationBar(cbm);
}
+
// write to database on a separate service thread
Intent dbWriteIntent = new Intent(this, CellBroadcastDatabaseService.class);
dbWriteIntent.setAction(CellBroadcastDatabaseService.ACTION_INSERT_NEW_BROADCAST);
@@ -146,61 +121,155 @@ public class CellBroadcastAlertService extends Service {
* @return true if the user has enabled this message type; false otherwise
*/
private boolean isMessageEnabledByUser(CellBroadcastMessage message) {
- switch (message.getMessageIdentifier()) {
- case SmsCbConstants.MESSAGE_ID_ETWS_TEST_MESSAGE:
- return PreferenceManager.getDefaultSharedPreferences(this)
- .getBoolean(CellBroadcastSettings.KEY_ENABLE_ETWS_TEST_ALERTS, false);
-
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
- return PreferenceManager.getDefaultSharedPreferences(this)
- .getBoolean(CellBroadcastSettings.KEY_ENABLE_CMAS_IMMINENT_THREAT_ALERTS,
- true);
-
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY:
- return PreferenceManager.getDefaultSharedPreferences(this)
- .getBoolean(CellBroadcastSettings.KEY_ENABLE_CMAS_AMBER_ALERTS, false);
-
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST:
- return PreferenceManager.getDefaultSharedPreferences(this)
- .getBoolean(CellBroadcastSettings.KEY_ENABLE_CMAS_TEST_ALERTS, false);
-
- default:
- return true;
+ if (message.isEtwsTestMessage()) {
+ return PreferenceManager.getDefaultSharedPreferences(this)
+ .getBoolean(CellBroadcastSettings.KEY_ENABLE_ETWS_TEST_ALERTS, false);
+ }
+
+ if (message.isCmasMessage()) {
+ switch (message.getCmasMessageClass()) {
+ case SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT:
+ return PreferenceManager.getDefaultSharedPreferences(this).getBoolean(
+ CellBroadcastSettings.KEY_ENABLE_CMAS_EXTREME_THREAT_ALERTS, true);
+
+ case SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT:
+ return PreferenceManager.getDefaultSharedPreferences(this).getBoolean(
+ CellBroadcastSettings.KEY_ENABLE_CMAS_SEVERE_THREAT_ALERTS, true);
+
+ case SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY:
+ return PreferenceManager.getDefaultSharedPreferences(this)
+ .getBoolean(CellBroadcastSettings.KEY_ENABLE_CMAS_AMBER_ALERTS, true);
+
+ case SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST:
+ case SmsCbCmasInfo.CMAS_CLASS_CMAS_EXERCISE:
+ return PreferenceManager.getDefaultSharedPreferences(this)
+ .getBoolean(CellBroadcastSettings.KEY_ENABLE_CMAS_TEST_ALERTS, false);
+
+ default:
+ return true; // presidential-level CMAS alerts are always enabled
+ }
}
+
+ return true; // other broadcast messages are always enabled
}
- private void addToNotificationBar(CellBroadcastMessage message) {
+ private void acquireTimedWakelock(int timeout) {
+ if (mWakeLock == null) {
+ PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+ // Note: acquiring a PARTIAL_WAKE_LOCK and setting window flag FLAG_TURN_SCREEN_ON in
+ // CellBroadcastAlertFullScreen is not sufficient to turn on the screen by itself.
+ // Use SCREEN_BRIGHT_WAKE_LOCK here as a workaround to ensure the screen turns on.
+ mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK
+ | PowerManager.ACQUIRE_CAUSES_WAKEUP, TAG);
+ }
+ mWakeLock.acquire(timeout);
+ }
+
+ /**
+ * Display a full-screen alert message for emergency alerts.
+ * @param message the alert to display
+ */
+ private void openEmergencyAlertNotification(CellBroadcastMessage message) {
+ // Acquire a CPU wake lock until the alert dialog and audio start playing.
+ acquireTimedWakelock(WAKE_LOCK_TIMEOUT);
+
+ // Close dialogs and window shade
+ Intent closeDialogs = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+ sendBroadcast(closeDialogs);
+
+ // start audio/vibration/speech service for emergency alerts
+ Intent audioIntent = new Intent(this, CellBroadcastAlertAudio.class);
+ audioIntent.setAction(CellBroadcastAlertAudio.ACTION_START_ALERT_AUDIO);
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ String duration = prefs.getString(CellBroadcastSettings.KEY_ALERT_SOUND_DURATION,
+ CellBroadcastSettings.ALERT_SOUND_DEFAULT_DURATION);
+ audioIntent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_DURATION_EXTRA,
+ Integer.parseInt(duration));
+
int channelTitleId = message.getDialogTitleResource();
CharSequence channelName = getText(channelTitleId);
String messageBody = message.getMessageBody();
- Notification notification = new Notification(R.drawable.stat_color_warning,
- channelName, System.currentTimeMillis());
+ if (prefs.getBoolean(CellBroadcastSettings.KEY_ENABLE_ALERT_SPEECH, true)) {
+ audioIntent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_MESSAGE_BODY, messageBody);
+
+ String language = message.getLanguageCode();
+ if (message.isEtwsMessage() && !"ja".equals(language)) {
+ Log.w(TAG, "bad language code for ETWS - using Japanese TTS");
+ language = "ja";
+ } else if (message.isCmasMessage() && !"en".equals(language)) {
+ Log.w(TAG, "bad language code for CMAS - using English TTS");
+ language = "en";
+ }
+ audioIntent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_MESSAGE_LANGUAGE,
+ language);
+ }
+ startService(audioIntent);
+
+ // Use lower 32 bits of emergency alert delivery time for notification ID
+ int notificationId = (int) message.getDeliveryTime();
+
+ // Decide which activity to start based on the state of the keyguard.
+ Class c = CellBroadcastAlertDialog.class;
+ KeyguardManager km = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
+ if (km.inKeyguardRestrictedInputMode()) {
+ // Use the full screen activity for security.
+ c = CellBroadcastAlertFullScreen.class;
+ }
- int notificationId = CellBroadcastReceiverApp.getCellBroadcastReceiverApp()
- .getNextNotificationId();
+ Intent notify = createDisplayMessageIntent(this, c, message, notificationId);
+ PendingIntent pi = PendingIntent.getActivity(this, notificationId, notify, 0);
- PendingIntent pi = PendingIntent.getActivity(this, 0, createDisplayMessageIntent(
- this, message, notificationId), 0);
+ Notification.Builder builder = new Notification.Builder(this)
+ .setSmallIcon(R.drawable.stat_color_warning)
+ .setTicker(getText(message.getDialogTitleResource()))
+ .setWhen(System.currentTimeMillis())
+ .setContentIntent(pi)
+ .setFullScreenIntent(pi, true)
+ .setContentTitle(channelName)
+ .setContentText(messageBody)
+ .setDefaults(Notification.DEFAULT_LIGHTS);
+
+ NotificationManager notificationManager =
+ (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
+
+ notificationManager.notify(notificationId, builder.getNotification());
+ }
+
+ /**
+ * Add the new alert to the notification bar (non-emergency alerts), or launch a
+ * high-priority immediate intent for emergency alerts.
+ * @param message the alert to display
+ */
+ private void addToNotificationBar(CellBroadcastMessage message) {
+ int channelTitleId = message.getDialogTitleResource();
+ CharSequence channelName = getText(channelTitleId);
+ String messageBody = message.getMessageBody();
- notification.setLatestEventInfo(this, channelName, messageBody, pi);
+ // Use the same ID to create a single notification for multiple non-emergency alerts.
+ int notificationId = NOTIFICATION_ID;
- if (message.isEmergencyAlertMessage() || CellBroadcastConfigService
- .isOperatorDefinedEmergencyId(message.getMessageIdentifier())) {
- // Emergency: open notification immediately
- notification.fullScreenIntent = pi;
- // use default notification lights (CellBroadcastAlertAudio plays sound/vibration)
- notification.defaults = Notification.DEFAULT_LIGHTS;
+ PendingIntent pi = PendingIntent.getActivity(this, 0, createDisplayMessageIntent(
+ this, CellBroadcastListActivity.class, message, notificationId), 0);
+
+ // use default sound/vibration/lights for non-emergency broadcasts
+ Notification.Builder builder = new Notification.Builder(this)
+ .setSmallIcon(R.drawable.stat_color_warning)
+ .setTicker(channelName)
+ .setWhen(System.currentTimeMillis())
+ .setContentIntent(pi)
+ .setDefaults(Notification.DEFAULT_ALL);
+
+ builder.setDefaults(Notification.DEFAULT_ALL);
+
+ // increment unread alert count (decremented when user dismisses alert dialog)
+ int unreadCount = CellBroadcastReceiverApp.incrementUnreadAlertCount();
+ if (unreadCount > 1) {
+ // use generic count of unread broadcasts if more than one unread
+ builder.setContentTitle(getString(R.string.notification_multiple_title));
+ builder.setContentText(getString(R.string.notification_multiple, unreadCount));
} else {
- // use default sound/vibration/lights for non-emergency broadcasts
- notification.defaults = Notification.DEFAULT_ALL;
+ builder.setContentTitle(channelName).setContentText(messageBody);
}
Log.i(TAG, "addToNotificationBar notificationId: " + notificationId);
@@ -208,15 +277,16 @@ public class CellBroadcastAlertService extends Service {
NotificationManager notificationManager =
(NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
- notificationManager.notify(notificationId, notification);
+ notificationManager.notify(notificationId, builder.getNotification());
}
- static Intent createDisplayMessageIntent(Context context,
+ static Intent createDisplayMessageIntent(Context context, Class intentClass,
CellBroadcastMessage message, int notificationId) {
// Trigger the list activity to fire up a dialog that shows the received messages
- Intent intent = new Intent(context, CellBroadcastListActivity.class);
+ Intent intent = new Intent(context, intentClass);
intent.putExtra(CellBroadcastMessage.SMS_CB_MESSAGE_EXTRA, message);
intent.putExtra(SMS_CB_NOTIFICATION_ID_EXTRA, notificationId);
+ intent.putExtra(NEW_ALERT_EXTRA, true);
// This line is needed to make this intent compare differently than the other intents
// created here for other messages. Without this line, the PendingIntent always gets the
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertWakeLock.java b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertWakeLock.java
deleted file mode 100644
index 2d23031cf..000000000
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertWakeLock.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2011 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.cellbroadcastreceiver;
-
-import android.content.Context;
-import android.os.PowerManager;
-
-/**
- * Hold a wakelock while showing the alert dialog and playing sound.
- */
-class CellBroadcastAlertWakeLock {
-
- private static PowerManager.WakeLock sCpuWakeLock;
-
- private CellBroadcastAlertWakeLock() {}
-
- static void acquireCpuWakeLock(Context context) {
- if (sCpuWakeLock != null) {
- return;
- }
-
- PowerManager pm =
- (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-
- sCpuWakeLock = pm.newWakeLock(
- PowerManager.PARTIAL_WAKE_LOCK |
- PowerManager.ACQUIRE_CAUSES_WAKEUP |
- PowerManager.ON_AFTER_RELEASE, "CellBroadcastAlertWakeLock");
- sCpuWakeLock.acquire();
- }
-
- static void releaseCpuLock() {
- if (sCpuWakeLock != null) {
- sCpuWakeLock.release();
- sCpuWakeLock = null;
- }
- }
-}
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastConfigService.java b/src/com/android/cellbroadcastreceiver/CellBroadcastConfigService.java
index 112dfc7f9..53674ebe4 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastConfigService.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastConfigService.java
@@ -22,12 +22,11 @@ import android.content.SharedPreferences;
import android.content.res.Resources;
import android.os.SystemProperties;
import android.preference.PreferenceManager;
-import android.telephony.SmsCbConstants;
import android.telephony.SmsManager;
import android.text.TextUtils;
import android.util.Log;
-import com.android.internal.telephony.gsm.SmsCbHeader;
+import com.android.internal.telephony.gsm.SmsCbConstants;
import static com.android.cellbroadcastreceiver.CellBroadcastReceiver.DBG;
@@ -51,7 +50,7 @@ public class CellBroadcastConfigService extends IntentService {
super(TAG); // use class name for worker thread name
}
- private void setChannelRange(SmsManager manager, String ranges, boolean enable) {
+ private static void setChannelRange(SmsManager manager, String ranges, boolean enable) {
try {
for (String channelRange : ranges.split(",")) {
int dashIndex = channelRange.indexOf('-');
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastListAdapter.java b/src/com/android/cellbroadcastreceiver/CellBroadcastCursorAdapter.java
index d0b7964f1..9641c64fc 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastListAdapter.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastCursorAdapter.java
@@ -26,11 +26,11 @@ import android.widget.CursorAdapter;
/**
* The back-end data adapter for {@link CellBroadcastListActivity}.
*/
-public class CellBroadcastListAdapter extends CursorAdapter {
- private static final String TAG = "CellBroadcastListAdapter";
+public class CellBroadcastCursorAdapter extends CursorAdapter {
- public CellBroadcastListAdapter(Context context, Cursor cursor) {
- super(context, cursor, true);
+ public CellBroadcastCursorAdapter(Context context, Cursor cursor) {
+ // don't set FLAG_AUTO_REQUERY or FLAG_REGISTER_CONTENT_OBSERVER
+ super(context, cursor, 0);
}
/**
@@ -41,6 +41,7 @@ public class CellBroadcastListAdapter extends CursorAdapter {
* @param parent The parent to which the new view is attached to
* @return the newly created view.
*/
+ @Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
CellBroadcastMessage message = CellBroadcastMessage.createFromCursor(cursor);
@@ -59,6 +60,7 @@ public class CellBroadcastListAdapter extends CursorAdapter {
* @param cursor The cursor from which to get the data. The cursor is already
* moved to the correct position.
*/
+ @Override
public void bindView(View view, Context context, Cursor cursor) {
CellBroadcastMessage message = CellBroadcastMessage.createFromCursor(cursor);
CellBroadcastListItem listItem = (CellBroadcastListItem) view;
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastCursorLoader.java b/src/com/android/cellbroadcastreceiver/CellBroadcastCursorLoader.java
new file mode 100644
index 000000000..2501336bc
--- /dev/null
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastCursorLoader.java
@@ -0,0 +1,181 @@
+/*
+ * 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.cellbroadcastreceiver;
+
+import android.content.AsyncTaskLoader;
+import android.content.CancellationSignal;
+import android.content.Context;
+import android.content.OperationCanceledException;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.util.Log;
+
+import static com.android.cellbroadcastreceiver.CellBroadcastReceiver.DBG;
+
+/**
+ * Async task loader for Cell Broadcast database cursors. Based on
+ * {@link android.content.CursorLoader}, which is used to query content providers.
+ */
+class CellBroadcastCursorLoader extends AsyncTaskLoader<Cursor> {
+
+ static final String TAG = "CellBroadcastCursorLoader";
+ static final String TABLE_NAME = "broadcasts";
+
+ private SQLiteDatabase mDatabase;
+
+ Cursor mCursor;
+ CancellationSignal mCancellationSignal;
+
+ /**
+ * Creates an empty cell broadcast cursor loader. The query is hardcoded into the class.
+ */
+ public CellBroadcastCursorLoader(Context context) {
+ super(context);
+ }
+
+ /**
+ * Called on a worker thread to perform the actual load and to return the result of the load
+ * operation.
+ *
+ * @return The result of the load operation.
+ * @throws android.content.OperationCanceledException
+ * if the load is canceled during execution.
+ * @see #isLoadInBackgroundCanceled
+ * @see #cancelLoadInBackground
+ * @see #onCanceled
+ */
+ @Override
+ public Cursor loadInBackground() {
+ synchronized (this) {
+ if (isLoadInBackgroundCanceled()) {
+ throw new OperationCanceledException();
+ }
+ mCancellationSignal = new CancellationSignal();
+ }
+ try {
+ if (mDatabase == null) {
+ if (DBG) Log.d(TAG, "loadInBackground: opening SQLite database");
+ mDatabase = new CellBroadcastDatabase.DatabaseHelper(getContext())
+ .getReadableDatabase();
+ }
+ Cursor cursor = mDatabase.query(false, TABLE_NAME,
+ CellBroadcastDatabase.Columns.QUERY_COLUMNS, null, null, null, null,
+ CellBroadcastDatabase.Columns.DELIVERY_TIME + " DESC", null,
+ mCancellationSignal);
+
+ if (cursor != null) {
+ // Ensure the cursor window is filled
+ int count = cursor.getCount();
+ if (DBG) Log.d(TAG, "loadInBackground() cursor row count = " + count);
+ }
+ return cursor;
+ } finally {
+ synchronized (this) {
+ mCancellationSignal = null;
+ }
+ }
+ }
+
+ @Override
+ public void cancelLoadInBackground() {
+ super.cancelLoadInBackground();
+
+ synchronized (this) {
+ if (mCancellationSignal != null) {
+ mCancellationSignal.cancel();
+ }
+ }
+ }
+
+ /* Runs on the UI thread */
+ @Override
+ public void deliverResult(Cursor cursor) {
+ if (isReset()) {
+ if (DBG) Log.d(TAG, "isReset() is true, closing cursor and returning");
+ // An async query came in while the loader is stopped
+ if (cursor != null) {
+ cursor.close();
+ }
+ return;
+ }
+ Cursor oldCursor = mCursor;
+ mCursor = cursor;
+
+ if (isStarted()) {
+ if (DBG) Log.d(TAG, "isStarted() is true, delivering result");
+ super.deliverResult(cursor);
+ } else {
+ if (DBG) Log.d(TAG, "isStarted() is false, not delivering!!!");
+ }
+
+ if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {
+ if (DBG) Log.d(TAG, "closing the old cursor...");
+ oldCursor.close();
+ }
+ }
+
+ /**
+ * Starts an asynchronous query of the database. When the result is ready the callbacks
+ * will be called on the UI thread. If a previous load has been completed and is still valid
+ * the result may be passed to the callbacks immediately.
+ *
+ * Must be called from the UI thread
+ */
+ @Override
+ protected void onStartLoading() {
+ if (mCursor != null) {
+ Log.d(TAG, "onStartLoading() delivering existing cursor");
+ deliverResult(mCursor);
+ }
+ if (takeContentChanged() || mCursor == null) {
+ Log.d(TAG, "onStartLoading() calling forceLoad()");
+ forceLoad();
+ }
+ }
+
+ /**
+ * Must be called from the UI thread
+ */
+ @Override
+ protected void onStopLoading() {
+ // Attempt to cancel the current load task if possible.
+ cancelLoad();
+ Log.d(TAG, "onStopLoading() called cancelLoad()");
+ }
+
+ @Override
+ public void onCanceled(Cursor cursor) {
+ if (cursor != null && !cursor.isClosed()) {
+ cursor.close();
+ Log.d(TAG, "onCanceled() closed the cursor");
+ }
+ }
+
+ @Override
+ protected void onReset() {
+ super.onReset();
+
+ // Ensure the loader is stopped
+ onStopLoading();
+
+ if (mCursor != null && !mCursor.isClosed()) {
+ mCursor.close();
+ Log.d(TAG, "onReset() closed the cursor");
+ }
+ mCursor = null;
+ }
+}
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastDatabase.java b/src/com/android/cellbroadcastreceiver/CellBroadcastDatabase.java
index a27be4b76..0eb48c292 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastDatabase.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastDatabase.java
@@ -16,21 +16,37 @@
package com.android.cellbroadcastreceiver;
+import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.provider.BaseColumns;
+import android.telephony.SmsCbCmasInfo;
+import android.telephony.SmsCbEtwsInfo;
+import android.telephony.SmsCbMessage;
+import android.util.Log;
+
+import com.android.internal.telephony.gsm.SmsCbConstants;
public class CellBroadcastDatabase {
- private static final String TAG = "CellBroadcastDatabase";
+ // package local for efficient access from inner class
+ static final String TAG = "CellBroadcastDatabase";
private CellBroadcastDatabase() {}
static final String DATABASE_NAME = "cell_broadcasts.db";
static final String TABLE_NAME = "broadcasts";
- static final int DATABASE_VERSION = 1;
+ /** Temporary table for upgrading the database version. */
+ static final String TEMP_TABLE_NAME = "old_broadcasts";
+
+ /**
+ * Database version 1: initial version
+ * Database version 2-9: (reserved for OEM database customization)
+ * Database version 10: adds ETWS and CMAS columns and CDMA support
+ */
+ static final int DATABASE_VERSION = 10;
static final class Columns implements BaseColumns {
@@ -49,16 +65,43 @@ public class CellBroadcastDatabase {
public static final String SERIAL_NUMBER = "serial_number";
/**
- * Message code.
+ * PLMN of broadcast sender. (SERIAL_NUMBER + PLMN + LAC + CID) uniquely identifies a
+ * broadcast for duplicate detection purposes.
+ * <P>Type: TEXT</P>
+ */
+ public static final String PLMN = "plmn";
+
+ /**
+ * Location Area (GSM) or Service Area (UMTS) of broadcast sender. Unused for CDMA.
+ * Only included if Geographical Scope of message is not PLMN wide (01).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String LAC = "lac";
+
+ /**
+ * Cell ID of message sender (GSM/UMTS). Unused for CDMA. Only included when the
+ * Geographical Scope of message is cell wide (00 or 11).
* <P>Type: INTEGER</P>
*/
- public static final String MESSAGE_CODE = "message_code";
+ public static final String CID = "cid";
/**
- * Message identifier.
+ * Message code (OBSOLETE: merged into SERIAL_NUMBER).
* <P>Type: INTEGER</P>
*/
- public static final String MESSAGE_IDENTIFIER = "message_id";
+ public static final String V1_MESSAGE_CODE = "message_code";
+
+ /**
+ * Message identifier (OBSOLETE: renamed to SERVICE_CATEGORY).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String V1_MESSAGE_IDENTIFIER = "message_id";
+
+ /**
+ * Service category (GSM/UMTS message identifier, CDMA service category).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String SERVICE_CATEGORY = "service_category";
/**
* Message language code.
@@ -85,18 +128,83 @@ public class CellBroadcastDatabase {
public static final String MESSAGE_READ = "read";
/**
+ * Message format (3GPP or 3GPP2).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MESSAGE_FORMAT = "format";
+
+ /**
+ * Message priority (including emergency).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MESSAGE_PRIORITY = "priority";
+
+ /**
+ * ETWS warning type (ETWS alerts only).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String ETWS_WARNING_TYPE = "etws_warning_type";
+
+ /**
+ * CMAS message class (CMAS alerts only).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CMAS_MESSAGE_CLASS = "cmas_message_class";
+
+ /**
+ * CMAS category (CMAS alerts only).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CMAS_CATEGORY = "cmas_category";
+
+ /**
+ * CMAS response type (CMAS alerts only).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CMAS_RESPONSE_TYPE = "cmas_response_type";
+
+ /**
+ * CMAS severity (CMAS alerts only).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CMAS_SEVERITY = "cmas_severity";
+
+ /**
+ * CMAS urgency (CMAS alerts only).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CMAS_URGENCY = "cmas_urgency";
+
+ /**
+ * CMAS certainty (CMAS alerts only).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CMAS_CERTAINTY = "cmas_certainty";
+
+ /**
* Query for list view adapter.
*/
static final String[] QUERY_COLUMNS = {
_ID,
GEOGRAPHICAL_SCOPE,
+ PLMN,
+ LAC,
+ CID,
SERIAL_NUMBER,
- MESSAGE_CODE,
- MESSAGE_IDENTIFIER,
+ SERVICE_CATEGORY,
LANGUAGE_CODE,
MESSAGE_BODY,
DELIVERY_TIME,
MESSAGE_READ,
+ MESSAGE_FORMAT,
+ MESSAGE_PRIORITY,
+ ETWS_WARNING_TYPE,
+ CMAS_MESSAGE_CLASS,
+ CMAS_CATEGORY,
+ CMAS_RESPONSE_TYPE,
+ CMAS_SEVERITY,
+ CMAS_URGENCY,
+ CMAS_CERTAINTY
};
}
@@ -104,13 +212,24 @@ public class CellBroadcastDatabase {
static final int COLUMN_ID = 0;
static final int COLUMN_GEOGRAPHICAL_SCOPE = 1;
- static final int COLUMN_SERIAL_NUMBER = 2;
- static final int COLUMN_MESSAGE_CODE = 3;
- static final int COLUMN_MESSAGE_IDENTIFIER = 4;
- static final int COLUMN_LANGUAGE_CODE = 5;
- static final int COLUMN_MESSAGE_BODY = 6;
- static final int COLUMN_DELIVERY_TIME = 7;
- static final int COLUMN_MESSAGE_READ = 8;
+ static final int COLUMN_PLMN = 2;
+ static final int COLUMN_LAC = 3;
+ static final int COLUMN_CID = 4;
+ static final int COLUMN_SERIAL_NUMBER = 5;
+ static final int COLUMN_SERVICE_CATEGORY = 6; // was COLUMN_MESSAGE_IDENTIFIER
+ static final int COLUMN_LANGUAGE_CODE = 7;
+ static final int COLUMN_MESSAGE_BODY = 8;
+ static final int COLUMN_DELIVERY_TIME = 9;
+ static final int COLUMN_MESSAGE_READ = 10;
+ static final int COLUMN_MESSAGE_FORMAT = 11;
+ static final int COLUMN_MESSAGE_PRIORITY = 12;
+ static final int COLUMN_ETWS_WARNING_TYPE = 13;
+ static final int COLUMN_CMAS_MESSAGE_CLASS = 14;
+ static final int COLUMN_CMAS_CATEGORY = 15;
+ static final int COLUMN_CMAS_RESPONSE_TYPE = 16;
+ static final int COLUMN_CMAS_SEVERITY = 17;
+ static final int COLUMN_CMAS_URGENCY = 18;
+ static final int COLUMN_CMAS_CERTAINTY = 19;
static class DatabaseHelper extends SQLiteOpenHelper {
@@ -123,28 +242,242 @@ public class CellBroadcastDatabase {
db.execSQL("CREATE TABLE " + TABLE_NAME + " ("
+ Columns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ Columns.GEOGRAPHICAL_SCOPE + " INTEGER,"
+ + Columns.PLMN + " TEXT,"
+ + Columns.LAC + " INTEGER,"
+ + Columns.CID + " INTEGER,"
+ Columns.SERIAL_NUMBER + " INTEGER,"
- + Columns.MESSAGE_CODE + " INTEGER,"
- + Columns.MESSAGE_IDENTIFIER + " INTEGER,"
+ + Columns.SERVICE_CATEGORY + " INTEGER,"
+ Columns.LANGUAGE_CODE + " TEXT,"
+ Columns.MESSAGE_BODY + " TEXT,"
+ Columns.DELIVERY_TIME + " INTEGER,"
- + Columns.MESSAGE_READ + " INTEGER);");
+ + Columns.MESSAGE_READ + " INTEGER,"
+ + Columns.MESSAGE_FORMAT + " INTEGER,"
+ + Columns.MESSAGE_PRIORITY + " INTEGER,"
+ + Columns.ETWS_WARNING_TYPE + " INTEGER,"
+ + Columns.CMAS_MESSAGE_CLASS + " INTEGER,"
+ + Columns.CMAS_CATEGORY + " INTEGER,"
+ + Columns.CMAS_RESPONSE_TYPE + " INTEGER,"
+ + Columns.CMAS_SEVERITY + " INTEGER,"
+ + Columns.CMAS_URGENCY + " INTEGER,"
+ + Columns.CMAS_CERTAINTY + " INTEGER);");
}
+ /** Columns to copy on database upgrade. */
+ private static final String[] COLUMNS_V1 = {
+ Columns.GEOGRAPHICAL_SCOPE,
+ Columns.SERIAL_NUMBER,
+ Columns.V1_MESSAGE_CODE,
+ Columns.V1_MESSAGE_IDENTIFIER,
+ Columns.LANGUAGE_CODE,
+ Columns.MESSAGE_BODY,
+ Columns.DELIVERY_TIME,
+ Columns.MESSAGE_READ,
+ };
+
+ private static final int COLUMN_V1_GEOGRAPHICAL_SCOPE = 0;
+ private static final int COLUMN_V1_SERIAL_NUMBER = 1;
+ private static final int COLUMN_V1_MESSAGE_CODE = 2;
+ private static final int COLUMN_V1_MESSAGE_IDENTIFIER = 3;
+ private static final int COLUMN_V1_LANGUAGE_CODE = 4;
+ private static final int COLUMN_V1_MESSAGE_BODY = 5;
+ private static final int COLUMN_V1_DELIVERY_TIME = 6;
+ private static final int COLUMN_V1_MESSAGE_READ = 7;
+
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- // ignored for now
+ if (oldVersion == newVersion) {
+ return;
+ }
+ Log.i(TAG, "Upgrading DB from version " + oldVersion + " to " + newVersion);
+
+ if (oldVersion == 1) {
+ db.beginTransaction();
+ try {
+ // Step 1: rename original table
+ db.execSQL("DROP TABLE IF EXISTS " + TEMP_TABLE_NAME);
+ db.execSQL("ALTER TABLE " + TABLE_NAME + " RENAME TO " + TEMP_TABLE_NAME);
+
+ // Step 2: create new table and indices
+ onCreate(db);
+
+ // Step 3: copy each message into the new table
+ Cursor cursor = db.query(TEMP_TABLE_NAME, COLUMNS_V1, null, null, null, null,
+ null);
+ try {
+ while (cursor.moveToNext()) {
+ upgradeMessageV1ToV2(db, cursor);
+ }
+ } finally {
+ cursor.close();
+ }
+
+ // Step 4: drop the original table and commit transaction
+ db.execSQL("DROP TABLE " + TEMP_TABLE_NAME);
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ oldVersion = 2;
+ }
}
- }
- /**
- * Returns a Cursor for the list view adapter, in reverse chronological order.
- * @param db an open readable database
- * @return the cursor for the list view adapter
- */
- static Cursor getCursor(SQLiteDatabase db) {
- return db.query(false, TABLE_NAME, Columns.QUERY_COLUMNS,
- null, null, null, null, Columns.DELIVERY_TIME + " DESC", null);
+ /**
+ * Upgrades a single broadcast message from version 1 to version 2.
+ */
+ private static void upgradeMessageV1ToV2(SQLiteDatabase db, Cursor cursor) {
+ int geographicalScope = cursor.getInt(COLUMN_V1_GEOGRAPHICAL_SCOPE);
+ int updateNumber = cursor.getInt(COLUMN_V1_SERIAL_NUMBER);
+ int messageCode = cursor.getInt(COLUMN_V1_MESSAGE_CODE);
+ int messageId = cursor.getInt(COLUMN_V1_MESSAGE_IDENTIFIER);
+ String languageCode = cursor.getString(COLUMN_V1_LANGUAGE_CODE);
+ String messageBody = cursor.getString(COLUMN_V1_MESSAGE_BODY);
+ long deliveryTime = cursor.getLong(COLUMN_V1_DELIVERY_TIME);
+ boolean isRead = (cursor.getInt(COLUMN_V1_MESSAGE_READ) != 0);
+
+ int serialNumber = ((geographicalScope & 0x03) << 14)
+ | ((messageCode & 0x3ff) << 4) | (updateNumber & 0x0f);
+
+ ContentValues cv = new ContentValues(16);
+ cv.put(Columns.GEOGRAPHICAL_SCOPE, geographicalScope);
+ cv.put(Columns.SERIAL_NUMBER, serialNumber);
+ cv.put(Columns.SERVICE_CATEGORY, messageId);
+ cv.put(Columns.LANGUAGE_CODE, languageCode);
+ cv.put(Columns.MESSAGE_BODY, messageBody);
+ cv.put(Columns.DELIVERY_TIME, deliveryTime);
+ cv.put(Columns.MESSAGE_READ, isRead);
+ cv.put(Columns.MESSAGE_FORMAT, SmsCbMessage.MESSAGE_FORMAT_3GPP);
+
+ int etwsWarningType = SmsCbEtwsInfo.ETWS_WARNING_TYPE_UNKNOWN;
+ int cmasMessageClass = SmsCbCmasInfo.CMAS_CLASS_UNKNOWN;
+ int cmasSeverity = SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN;
+ int cmasUrgency = SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN;
+ int cmasCertainty = SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN;
+ switch (messageId) {
+ case SmsCbConstants.MESSAGE_ID_ETWS_EARTHQUAKE_WARNING:
+ etwsWarningType = SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE;
+ break;
+
+ case SmsCbConstants.MESSAGE_ID_ETWS_TSUNAMI_WARNING:
+ etwsWarningType = SmsCbEtwsInfo.ETWS_WARNING_TYPE_TSUNAMI;
+ break;
+
+ case SmsCbConstants.MESSAGE_ID_ETWS_EARTHQUAKE_AND_TSUNAMI_WARNING:
+ etwsWarningType = SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI;
+ break;
+
+ case SmsCbConstants.MESSAGE_ID_ETWS_TEST_MESSAGE:
+ etwsWarningType = SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE;
+ break;
+
+ case SmsCbConstants.MESSAGE_ID_ETWS_OTHER_EMERGENCY_TYPE:
+ etwsWarningType = SmsCbEtwsInfo.ETWS_WARNING_TYPE_OTHER_EMERGENCY;
+ break;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL:
+ cmasMessageClass = SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT;
+ break;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
+ cmasMessageClass = SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT;
+ cmasSeverity = SmsCbCmasInfo.CMAS_SEVERITY_EXTREME;
+ cmasUrgency = SmsCbCmasInfo.CMAS_URGENCY_IMMEDIATE;
+ cmasCertainty = SmsCbCmasInfo.CMAS_CERTAINTY_OBSERVED;
+ break;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
+ cmasMessageClass = SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT;
+ cmasSeverity = SmsCbCmasInfo.CMAS_SEVERITY_EXTREME;
+ cmasUrgency = SmsCbCmasInfo.CMAS_URGENCY_IMMEDIATE;
+ cmasCertainty = SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY;
+ break;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
+ cmasMessageClass = SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT;
+ cmasSeverity = SmsCbCmasInfo.CMAS_SEVERITY_EXTREME;
+ cmasUrgency = SmsCbCmasInfo.CMAS_URGENCY_EXPECTED;
+ cmasCertainty = SmsCbCmasInfo.CMAS_CERTAINTY_OBSERVED;
+ break;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
+ cmasMessageClass = SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT;
+ cmasSeverity = SmsCbCmasInfo.CMAS_SEVERITY_EXTREME;
+ cmasUrgency = SmsCbCmasInfo.CMAS_URGENCY_EXPECTED;
+ cmasCertainty = SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY;
+ break;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
+ cmasMessageClass = SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT;
+ cmasSeverity = SmsCbCmasInfo.CMAS_SEVERITY_SEVERE;
+ cmasUrgency = SmsCbCmasInfo.CMAS_URGENCY_IMMEDIATE;
+ cmasCertainty = SmsCbCmasInfo.CMAS_CERTAINTY_OBSERVED;
+ break;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
+ cmasMessageClass = SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT;
+ cmasSeverity = SmsCbCmasInfo.CMAS_SEVERITY_SEVERE;
+ cmasUrgency = SmsCbCmasInfo.CMAS_URGENCY_IMMEDIATE;
+ cmasCertainty = SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY;
+ break;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
+ cmasMessageClass = SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT;
+ cmasSeverity = SmsCbCmasInfo.CMAS_SEVERITY_SEVERE;
+ cmasUrgency = SmsCbCmasInfo.CMAS_URGENCY_EXPECTED;
+ cmasCertainty = SmsCbCmasInfo.CMAS_CERTAINTY_OBSERVED;
+ break;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
+ cmasMessageClass = SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT;
+ cmasSeverity = SmsCbCmasInfo.CMAS_SEVERITY_SEVERE;
+ cmasUrgency = SmsCbCmasInfo.CMAS_URGENCY_EXPECTED;
+ cmasCertainty = SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY;
+ break;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY:
+ cmasMessageClass = SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY;
+ break;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST:
+ cmasMessageClass = SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST;
+ break;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXERCISE:
+ cmasMessageClass = SmsCbCmasInfo.CMAS_CLASS_CMAS_EXERCISE;
+ break;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE:
+ cmasMessageClass = SmsCbCmasInfo.CMAS_CLASS_OPERATOR_DEFINED_USE;
+ break;
+ }
+
+ if (etwsWarningType != SmsCbEtwsInfo.ETWS_WARNING_TYPE_UNKNOWN
+ || cmasMessageClass != SmsCbCmasInfo.CMAS_CLASS_UNKNOWN) {
+ cv.put(Columns.MESSAGE_PRIORITY, SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY);
+ } else {
+ cv.put(Columns.MESSAGE_PRIORITY, SmsCbMessage.MESSAGE_PRIORITY_NORMAL);
+ }
+
+ if (etwsWarningType != SmsCbEtwsInfo.ETWS_WARNING_TYPE_UNKNOWN) {
+ cv.put(Columns.ETWS_WARNING_TYPE, etwsWarningType);
+ }
+
+ if (cmasMessageClass != SmsCbCmasInfo.CMAS_CLASS_UNKNOWN) {
+ cv.put(Columns.CMAS_MESSAGE_CLASS, cmasMessageClass);
+ }
+
+ if (cmasSeverity != SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN) {
+ cv.put(Columns.CMAS_SEVERITY, cmasSeverity);
+ }
+
+ if (cmasUrgency != SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN) {
+ cv.put(Columns.CMAS_URGENCY, cmasUrgency);
+ }
+
+ if (cmasCertainty != SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN) {
+ cv.put(Columns.CMAS_CERTAINTY, cmasCertainty);
+ }
+
+ db.insert(TABLE_NAME, null, cv);
+ }
}
} \ No newline at end of file
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastDatabaseService.java b/src/com/android/cellbroadcastreceiver/CellBroadcastDatabaseService.java
index d27e21e67..2e1d5b521 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastDatabaseService.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastDatabaseService.java
@@ -20,8 +20,11 @@ import android.app.IntentService;
import android.content.ContentValues;
import android.content.Intent;
import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
import android.util.Log;
+import static com.android.cellbroadcastreceiver.CellBroadcastReceiver.DBG;
+
/**
* Service to update the SQLite database to add a new broadcast message,
* or to delete one or all previously received broadcasts.
@@ -49,46 +52,53 @@ public class CellBroadcastDatabaseService extends IntentService {
public static final String DATABASE_DELIVERY_TIME_EXTRA =
"com.android.cellbroadcastreceiver.DATABASE_DELIVERY_TIME";
+ /** Identifier for getExtra() to tell it to decrement the unread alert count. */
+ public static final String DECREMENT_UNREAD_ALERT_COUNT =
+ "com.android.cellbroadcastreceiver.DECREMENT_UNREAD_ALERT_COUNT";
+
private SQLiteDatabase mBroadcastDb;
/** Callback for the active list activity when the contents change. */
- private static CellBroadcastListActivity sActiveListActivity;
+ private static CellBroadcastListActivity.CursorLoaderListFragment sActiveListFragment;
public CellBroadcastDatabaseService() {
super(TAG); // use class name for worker thread name
}
@Override
- public void onCreate() {
- super.onCreate();
-
- if (mBroadcastDb == null) {
- CellBroadcastDatabase.DatabaseHelper helper =
- new CellBroadcastDatabase.DatabaseHelper(this);
- mBroadcastDb = helper.getWritableDatabase();
- }
- }
-
- @Override
public void onDestroy() {
super.onDestroy();
if (mBroadcastDb != null) {
+ if (DBG) Log.d(TAG, "onDestroy: closing mBroadcastDb...");
mBroadcastDb.close();
mBroadcastDb = null;
}
}
- static void setActiveListActivity(CellBroadcastListActivity activity) {
- sActiveListActivity = activity;
+ static void setActiveListFragment(CellBroadcastListActivity.CursorLoaderListFragment fragment) {
+ sActiveListFragment = fragment;
}
@Override
public void onHandleIntent(Intent intent) {
- // TODO: security check to detect malicious broadcast injections
+ // open the database on the worker thread
+ if (mBroadcastDb == null) {
+ if (DBG) Log.d(TAG, "onHandleIntent: opening mBroadcastDb...");
+ CellBroadcastDatabase.DatabaseHelper helper =
+ new CellBroadcastDatabase.DatabaseHelper(this);
+ try {
+ mBroadcastDb = helper.getWritableDatabase();
+ } catch (SQLiteException e) {
+ Log.e(TAG, "SQLite exception trying to open broadcasts db", e);
+ return;
+ }
+ }
+
String action = intent.getAction();
boolean notifyActiveListActivity = false;
if (ACTION_INSERT_NEW_BROADCAST.equals(action)) {
+ if (DBG) Log.d(TAG, "onHandleIntent: ACTION_INSERT_NEW_BROADCAST");
CellBroadcastMessage cbm = intent.getParcelableExtra(
CellBroadcastMessage.SMS_CB_MESSAGE_EXTRA);
if (cbm == null) {
@@ -96,6 +106,8 @@ public class CellBroadcastDatabaseService extends IntentService {
return;
}
+ if (DBG) Log.d(TAG, "new broadcast deliveryTime = " + cbm.getDeliveryTime());
+
ContentValues cv = cbm.getContentValues();
long rowId = mBroadcastDb.insert(CellBroadcastDatabase.TABLE_NAME, null, cv);
if (rowId == -1) {
@@ -104,6 +116,7 @@ public class CellBroadcastDatabaseService extends IntentService {
notifyActiveListActivity = true;
}
} else if (ACTION_DELETE_BROADCAST.equals(action)) {
+ if (DBG) Log.d(TAG, "onHandleIntent: ACTION_DELETE_BROADCAST");
long rowId = intent.getLongExtra(DATABASE_ROW_ID_EXTRA, -1);
if (rowId == -1) {
Log.e(TAG, "ACTION_DELETE_BROADCAST missing row ID to delete");
@@ -115,11 +128,17 @@ public class CellBroadcastDatabaseService extends IntentService {
new String[]{Long.toString(rowId)});
if (rowCount != 0) {
notifyActiveListActivity = true;
+ if (intent.getBooleanExtra(DECREMENT_UNREAD_ALERT_COUNT, false)) {
+ CellBroadcastReceiverApp.decrementUnreadAlertCount();
+ }
}
} else if (ACTION_DELETE_ALL_BROADCASTS.equals(action)) {
+ if (DBG) Log.d(TAG, "onHandleIntent: ACTION_DELETE_ALL_BROADCASTS");
mBroadcastDb.delete(CellBroadcastDatabase.TABLE_NAME, null, null);
+ CellBroadcastReceiverApp.resetUnreadAlertCount();
notifyActiveListActivity = true;
} else if (ACTION_MARK_BROADCAST_READ.equals(action)) {
+ if (DBG) Log.d(TAG, "onHandleIntent: ACTION_MARK_BROADCAST_READ");
long rowId = intent.getLongExtra(DATABASE_ROW_ID_EXTRA, -1);
long deliveryTime = intent.getLongExtra(DATABASE_DELIVERY_TIME_EXTRA, -1);
if (rowId == -1 && deliveryTime == -1) {
@@ -128,24 +147,33 @@ public class CellBroadcastDatabaseService extends IntentService {
}
ContentValues cv = new ContentValues(1);
cv.put(CellBroadcastDatabase.Columns.MESSAGE_READ, 1);
- int rowCount;
+
+ // For new alerts, select by delivery time because the row ID is not known.
+ String whereClause;
+ String[] whereArgs;
if (rowId != -1) {
- rowCount = mBroadcastDb.update(CellBroadcastDatabase.TABLE_NAME, cv,
- CellBroadcastDatabase.Columns._ID + "=?",
- new String[]{Long.toString(rowId)});
+ whereClause = CellBroadcastDatabase.Columns._ID + "=?";
+ whereArgs = new String[]{Long.toString(rowId)};
} else {
- rowCount = mBroadcastDb.update(CellBroadcastDatabase.TABLE_NAME, cv,
- CellBroadcastDatabase.Columns.DELIVERY_TIME + "=?",
- new String[]{Long.toString(deliveryTime)});
+ whereClause = CellBroadcastDatabase.Columns.DELIVERY_TIME + "=?";
+ whereArgs = new String[]{Long.toString(deliveryTime)};
}
+
+ int rowCount = mBroadcastDb.update(CellBroadcastDatabase.TABLE_NAME, cv,
+ whereClause, whereArgs);
+
if (rowCount != 0) {
notifyActiveListActivity = true;
+ } else {
+ Log.e(TAG, "MARK_BROADCAST_READ failed, rowId: " + rowId + " deliveryTime: "
+ + deliveryTime);
}
} else {
Log.e(TAG, "ignoring unexpected Intent with action " + action);
}
- if (notifyActiveListActivity && sActiveListActivity != null) {
- sActiveListActivity.databaseContentChanged();
+ if (notifyActiveListActivity && sActiveListFragment != null) {
+ if (DBG) Log.d(TAG, "notifying databaseContentChanged() for " + action);
+ sActiveListFragment.databaseContentChanged();
}
}
}
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastListActivity.java b/src/com/android/cellbroadcastreceiver/CellBroadcastListActivity.java
index eafeec298..e32b491aa 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastListActivity.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastListActivity.java
@@ -16,247 +16,301 @@
package com.android.cellbroadcastreceiver;
+import android.app.Activity;
import android.app.AlertDialog;
-import android.app.ListActivity;
+import android.app.FragmentManager;
+import android.app.ListFragment;
+import android.app.LoaderManager;
import android.app.NotificationManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
+import android.content.Loader;
import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
+import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
+import android.view.LayoutInflater;
import android.view.Menu;
+import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnCreateContextMenuListener;
+import android.view.ViewGroup;
+import android.widget.CursorAdapter;
import android.widget.ListView;
import android.widget.TextView;
/**
- * This activity provides a list view of received cell broadcasts.
+ * This activity provides a list view of received cell broadcasts. Most of the work is handled
+ * in the inner CursorLoaderListFragment class.
*/
-public class CellBroadcastListActivity extends ListActivity {
- private static final String TAG = "CellBroadcastListActivity";
+public class CellBroadcastListActivity extends Activity {
+ // package local for efficient access from inner class
+ static final String TAG = "CellBroadcastListActivity";
- // IDs of the main menu items.
- public static final int MENU_DELETE_ALL = 3;
- public static final int MENU_PREFERENCES = 4;
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
- // IDs of the context menu items for the list of broadcasts.
- public static final int MENU_DELETE = 0;
- public static final int MENU_VIEW = 1;
+ // Dismiss the notification that brought us here (if any).
+ ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE))
+ .cancel(CellBroadcastAlertService.NOTIFICATION_ID);
- private CellBroadcastListAdapter mListAdapter;
+ FragmentManager fm = getFragmentManager();
- private SQLiteDatabase mBroadcastDb;
+ // Create the list fragment and add it as our sole content.
+ if (fm.findFragmentById(android.R.id.content) == null) {
+ CursorLoaderListFragment listFragment = new CursorLoaderListFragment();
+ fm.beginTransaction().add(android.R.id.content, listFragment).commit();
+ }
+ }
- private Cursor mAdapterCursor;
+ /**
+ * List fragment queries SQLite database on worker thread.
+ */
+ public static class CursorLoaderListFragment extends ListFragment
+ implements LoaderManager.LoaderCallbacks<Cursor> {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.cell_broadcast_list_screen);
+ // IDs of the main menu items.
+ private static final int MENU_DELETE_ALL = 3;
+ private static final int MENU_PREFERENCES = 4;
+
+ // IDs of the context menu items (package local, accessed from inner DeleteThreadListener).
+ static final int MENU_DELETE = 0;
+ static final int MENU_VIEW = 1;
+
+ // This is the Adapter being used to display the list's data.
+ CursorAdapter mAdapter;
- ListView listView = getListView();
- listView.setOnCreateContextMenuListener(mOnCreateContextMenuListener);
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
- if (mBroadcastDb == null) {
- CellBroadcastDatabase.DatabaseHelper helper =
- new CellBroadcastDatabase.DatabaseHelper(this);
- mBroadcastDb = helper.getReadableDatabase();
+ // We have a menu item to show in action bar.
+ setHasOptionsMenu(true);
+
+ // Prepare the loader. Either re-connect with an existing one,
+ // or start a new one.
+ getLoaderManager().initLoader(0, null, this);
}
- if (mAdapterCursor == null) {
- mAdapterCursor = CellBroadcastDatabase.getCursor(mBroadcastDb);
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.cell_broadcast_list_screen, container, false);
}
- mListAdapter = new CellBroadcastListAdapter(this, mAdapterCursor);
- setListAdapter(mListAdapter);
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
- CellBroadcastDatabaseService.setActiveListActivity(this);
+ // Tell database service to notify us when a new broadcast arrives.
+ CellBroadcastDatabaseService.setActiveListFragment(this);
- parseIntent(getIntent());
- }
+ // Set context menu for long-press.
+ ListView listView = getListView();
+ listView.setOnCreateContextMenuListener(mOnCreateContextMenuListener);
- @Override
- protected void onDestroy() {
- if (mAdapterCursor != null) {
- mAdapterCursor.close();
- mAdapterCursor = null;
+ // Create a cursor adapter to display the loaded data.
+ mAdapter = new CellBroadcastCursorAdapter(getActivity(), null);
+ setListAdapter(mAdapter);
}
- if (mBroadcastDb != null) {
- mBroadcastDb.close();
- mBroadcastDb = null;
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ CellBroadcastDatabaseService.setActiveListFragment(null);
}
- CellBroadcastDatabaseService.setActiveListActivity(null);
- super.onDestroy();
- }
- /** Callback from CellBroadcastDatabaseService after content changes. */
- void databaseContentChanged() {
- runOnUiThread(new Runnable() {
- public void run() {
- mAdapterCursor.requery();
- }
- });
- }
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ menu.add(0, MENU_DELETE_ALL, 0, R.string.menu_delete_all).setIcon(
+ android.R.drawable.ic_menu_delete);
+ menu.add(0, MENU_PREFERENCES, 0, R.string.menu_preferences).setIcon(
+ android.R.drawable.ic_menu_preferences);
+ }
- @Override
- protected void onNewIntent(Intent intent) {
- // TODO: how do multiple messages stack together?
- // removeDialog(DIALOG_SHOW_MESSAGE);
- parseIntent(intent);
- }
+ @Override
+ public void onPrepareOptionsMenu(Menu menu) {
+ menu.findItem(MENU_DELETE_ALL).setVisible(!mAdapter.isEmpty());
+ }
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- menu.clear();
+ @Override
+ public void onListItemClick(ListView l, View v, int position, long id) {
+ CellBroadcastListItem cbli = (CellBroadcastListItem) v;
+ showDialogAndMarkRead(cbli.getMessage());
+ }
- if (mListAdapter.getCount() > 0) {
- menu.add(0, MENU_DELETE_ALL, 0, R.string.menu_delete_all).setIcon(
- android.R.drawable.ic_menu_delete);
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ return new CellBroadcastCursorLoader(getActivity());
}
- menu.add(0, MENU_PREFERENCES, 0, R.string.menu_preferences).setIcon(
- android.R.drawable.ic_menu_preferences);
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+ // Swap the new cursor in. (The framework will take care of closing the
+ // old cursor once we return.)
+ mAdapter.swapCursor(data);
+ }
- return true;
- }
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {
+ // This is called when the last Cursor provided to onLoadFinished()
+ // above is about to be closed. We need to make sure we are no
+ // longer using it.
+ mAdapter.swapCursor(null);
+ }
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch(item.getItemId()) {
- case MENU_DELETE_ALL:
- confirmDeleteThread(-1);
- break;
-
- case MENU_PREFERENCES:
- Intent intent = new Intent(this, CellBroadcastSettings.class);
- startActivityIfNeeded(intent, -1);
- break;
-
- default:
- return true;
+ /**
+ * Callback from CellBroadcastDatabaseService after content changes.
+ */
+ void databaseContentChanged() {
+ Loader<Cursor> loader = getLoaderManager().getLoader(0);
+ if (loader != null) {
+ loader.onContentChanged();
+ } else {
+ Log.w(TAG, "databaseContentChanged() called, but loader is null");
+ }
}
- return false;
- }
- @Override
- protected void onListItemClick(ListView l, View v, int position, long id) {
- Cursor cursor = mListAdapter.getCursor();
- if (cursor != null && cursor.getPosition() >= 0) {
- showDialogAndMarkRead(cursor);
+ private void showDialogAndMarkRead(CellBroadcastMessage cbm) {
+ // show emergency alerts with the warning icon, but don't play alert tone
+ Intent i = new Intent(getActivity(), CellBroadcastAlertDialog.class);
+ i.putExtra(CellBroadcastMessage.SMS_CB_MESSAGE_EXTRA, cbm);
+ startActivity(i);
}
- }
- private final OnCreateContextMenuListener mOnCreateContextMenuListener =
- new OnCreateContextMenuListener() {
- public void onCreateContextMenu(ContextMenu menu, View v,
- ContextMenuInfo menuInfo) {
- menu.add(0, MENU_VIEW, 0, R.string.menu_view);
- menu.add(0, MENU_DELETE, 0, R.string.menu_delete);
+ private final OnCreateContextMenuListener mOnCreateContextMenuListener =
+ new OnCreateContextMenuListener() {
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v,
+ ContextMenuInfo menuInfo) {
+ menu.setHeaderTitle(R.string.message_options);
+ menu.add(0, MENU_VIEW, 0, R.string.menu_view);
+ menu.add(0, MENU_DELETE, 0, R.string.menu_delete);
+ }
+ };
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ Cursor cursor = mAdapter.getCursor();
+ if (cursor != null && cursor.getPosition() >= 0) {
+ switch (item.getItemId()) {
+ case MENU_DELETE:
+ // We need to decrement the unread alert count if deleting unread alert
+ boolean isUnread =
+ (cursor.getInt(CellBroadcastDatabase.COLUMN_MESSAGE_READ) == 0);
+ confirmDeleteThread(cursor.getLong(CellBroadcastDatabase.COLUMN_ID),
+ isUnread);
+ break;
+
+ case MENU_VIEW:
+ showDialogAndMarkRead(CellBroadcastMessage.createFromCursor(cursor));
+ break;
+
+ default:
+ break;
}
- };
+ }
+ return super.onContextItemSelected(item);
+ }
- @Override
- public boolean onContextItemSelected(MenuItem item) {
- Cursor cursor = mListAdapter.getCursor();
- if (cursor != null && cursor.getPosition() >= 0) {
- switch (item.getItemId()) {
- case MENU_DELETE:
- confirmDeleteThread(cursor.getLong(CellBroadcastDatabase.COLUMN_ID));
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch(item.getItemId()) {
+ case MENU_DELETE_ALL:
+ confirmDeleteThread(-1, false);
break;
- case MENU_VIEW:
- showDialogAndMarkRead(cursor);
+ case MENU_PREFERENCES:
+ Intent intent = new Intent(getActivity(), CellBroadcastSettings.class);
+ startActivity(intent);
break;
default:
- break;
+ return true;
}
+ return false;
}
- return super.onContextItemSelected(item);
- }
- private void showDialogAndMarkRead(Cursor cursor) {
- CellBroadcastMessage cbm = CellBroadcastMessage.createFromCursor(cursor);
- boolean isAlertMessage = cbm.isPublicAlertMessage() || CellBroadcastConfigService
- .isOperatorDefinedEmergencyId(cbm.getMessageIdentifier());
- // show emergency alerts with the warning icon, but don't play alert tone
- CellBroadcastAlertDialog dialog = new CellBroadcastAlertDialog(this,
- cbm.getDialogTitleResource(), cbm.getMessageBody(),
- isAlertMessage, cbm.getDeliveryTime());
- dialog.show();
- }
-
- /**
- * Start the process of putting up a dialog to confirm deleting a broadcast.
- * @param rowId the row ID of the broadcast to delete, or -1 to delete all broadcasts
- */
- public void confirmDeleteThread(long rowId) {
- DeleteThreadListener listener = new DeleteThreadListener(rowId);
- confirmDeleteThreadDialog(listener, (rowId == -1), this);
- }
+ /**
+ * Start the process of putting up a dialog to confirm deleting a broadcast.
+ * @param rowId the row ID of the broadcast to delete, or -1 to delete all broadcasts
+ * @param unread true if the alert was not already marked as read
+ */
+ public void confirmDeleteThread(long rowId, boolean unread) {
+ DeleteThreadListener listener = new DeleteThreadListener(rowId, unread);
+ confirmDeleteThreadDialog(listener, (rowId == -1), getActivity());
+ }
- /**
- * Build and show the proper delete broadcast dialog. The UI is slightly different
- * depending on whether there are locked messages in the thread(s) and whether we're
- * deleting a single broadcast or all broadcasts.
- * @param listener gets called when the delete button is pressed
- * @param deleteAll whether to show a single thread or all threads UI
- * @param context used to load the various UI elements
- */
- public static void confirmDeleteThreadDialog(DeleteThreadListener listener,
- boolean deleteAll, Context context) {
- View contents = View.inflate(context, R.layout.delete_broadcast_dialog_view, null);
- TextView msg = (TextView)contents.findViewById(R.id.message);
- msg.setText(deleteAll
- ? R.string.confirm_delete_all_broadcasts
- : R.string.confirm_delete_broadcast);
-
- AlertDialog.Builder builder = new AlertDialog.Builder(context);
- builder.setTitle(R.string.confirm_dialog_title)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setCancelable(true)
- .setPositiveButton(R.string.button_delete, listener)
- .setNegativeButton(R.string.button_cancel, null)
- .setView(contents)
- .show();
- }
+ /**
+ * Build and show the proper delete broadcast dialog. The UI is slightly different
+ * depending on whether there are locked messages in the thread(s) and whether we're
+ * deleting a single broadcast or all broadcasts.
+ * @param listener gets called when the delete button is pressed
+ * @param deleteAll whether to show a single thread or all threads UI
+ * @param context used to load the various UI elements
+ */
+ public static void confirmDeleteThreadDialog(DeleteThreadListener listener,
+ boolean deleteAll, Context context) {
+ View contents = View.inflate(context, R.layout.delete_broadcast_dialog_view, null);
+ TextView msg = (TextView)contents.findViewById(R.id.message);
+ msg.setText(deleteAll
+ ? R.string.confirm_delete_all_broadcasts
+ : R.string.confirm_delete_broadcast);
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.confirm_dialog_title)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setCancelable(true)
+ .setPositiveButton(R.string.button_delete, listener)
+ .setNegativeButton(R.string.button_cancel, null)
+ .setView(contents)
+ .show();
+ }
- public class DeleteThreadListener implements OnClickListener {
- private final long mRowId;
+ public class DeleteThreadListener implements OnClickListener {
+ private final long mRowId;
+ private final boolean mIsUnread;
- public DeleteThreadListener(long rowId) {
- mRowId = rowId;
- }
+ public DeleteThreadListener(long rowId, boolean unread) {
+ mRowId = rowId;
+ mIsUnread = unread;
+ }
- public void onClick(DialogInterface dialog, int whichButton) {
- if (mRowId != -1) {
+ @Override
+ public void onClick(DialogInterface dialog, int whichButton) {
// delete from database on a separate service thread
- Intent dbWriteIntent = new Intent(CellBroadcastListActivity.this,
+ Intent dbWriteIntent = new Intent(getActivity(),
CellBroadcastDatabaseService.class);
- dbWriteIntent.setAction(CellBroadcastDatabaseService.ACTION_DELETE_BROADCAST);
- dbWriteIntent.putExtra(CellBroadcastDatabaseService.DATABASE_ROW_ID_EXTRA, mRowId);
- startService(dbWriteIntent);
- } else {
- // delete from database on a separate service thread
- Intent dbWriteIntent = new Intent(CellBroadcastListActivity.this,
- CellBroadcastDatabaseService.class);
- dbWriteIntent.setAction(CellBroadcastDatabaseService.ACTION_DELETE_ALL_BROADCASTS);
- startService(dbWriteIntent);
+ if (mRowId != -1) {
+ dbWriteIntent.setAction(CellBroadcastDatabaseService.ACTION_DELETE_BROADCAST);
+ dbWriteIntent.putExtra(CellBroadcastDatabaseService.DATABASE_ROW_ID_EXTRA,
+ mRowId);
+ if (mIsUnread) {
+ // decrement unread alert count after delete
+ dbWriteIntent.putExtra(
+ CellBroadcastDatabaseService.DECREMENT_UNREAD_ALERT_COUNT, true);
+ }
+ } else {
+ dbWriteIntent.setAction(
+ CellBroadcastDatabaseService.ACTION_DELETE_ALL_BROADCASTS);
+ }
+ getActivity().startService(dbWriteIntent);
+ dialog.dismiss();
}
- dialog.dismiss();
}
}
- private void parseIntent(Intent intent) {
+ @Override
+ protected void onNewIntent(Intent intent) {
if (intent == null) {
return;
}
+
Bundle extras = intent.getExtras();
if (extras == null) {
return;
@@ -270,12 +324,9 @@ public class CellBroadcastListActivity extends ListActivity {
(NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel(notificationId);
- boolean isEmergencyAlert = cbm.isPublicAlertMessage() || CellBroadcastConfigService
- .isOperatorDefinedEmergencyId(cbm.getMessageIdentifier());
-
- CellBroadcastAlertDialog dialog = new CellBroadcastAlertDialog(this,
- cbm.getDialogTitleResource(), cbm.getMessageBody(),
- isEmergencyAlert, cbm.getDeliveryTime());
- dialog.show();
+ // launch the dialog activity to show the alert
+ Intent i = new Intent(this, CellBroadcastAlertDialog.class);
+ i.putExtra(CellBroadcastMessage.SMS_CB_MESSAGE_EXTRA, cbm);
+ startActivity(i);
}
}
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastListItem.java b/src/com/android/cellbroadcastreceiver/CellBroadcastListItem.java
index 6f6423609..52aff0217 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastListItem.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastListItem.java
@@ -17,24 +17,18 @@
package com.android.cellbroadcastreceiver;
import android.content.Context;
-import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
-import android.text.style.StyleSpan;
import android.util.AttributeSet;
import android.view.accessibility.AccessibilityEvent;
import android.widget.RelativeLayout;
import android.widget.TextView;
-import java.util.List;
-
/**
- * This class manages the view for given conversation.
+ * This class manages the list item view for a single alert.
*/
public class CellBroadcastListItem extends RelativeLayout {
- private static final String TAG = "CellBroadcastListItem";
- private static final boolean DEBUG = false;
private CellBroadcastMessage mCbMessage;
@@ -42,12 +36,6 @@ public class CellBroadcastListItem extends RelativeLayout {
private TextView mMessageView;
private TextView mDateView;
- private static final StyleSpan STYLE_BOLD = new StyleSpan(Typeface.BOLD);
-
- public CellBroadcastListItem(Context context) {
- super(context);
- }
-
public CellBroadcastListItem(Context context, AttributeSet attrs) {
super(context, attrs);
}
@@ -90,7 +78,7 @@ public class CellBroadcastListItem extends RelativeLayout {
// Unread messages are shown in bold
if (!message.isRead()) {
- buf.setSpan(STYLE_BOLD, 0, buf.length(),
+ buf.setSpan(CellBroadcastMessage.STYLE_BOLD, 0, buf.length(),
Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
}
return buf;
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastMessage.java b/src/com/android/cellbroadcastreceiver/CellBroadcastMessage.java
index e38aa022a..f9ff4ec54 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastMessage.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastMessage.java
@@ -19,13 +19,17 @@ package com.android.cellbroadcastreceiver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
+import android.graphics.Typeface;
import android.os.Parcel;
import android.os.Parcelable;
-import android.telephony.SmsCbConstants;
+import android.telephony.SmsCbCmasInfo;
+import android.telephony.SmsCbEtwsInfo;
+import android.telephony.SmsCbLocation;
import android.telephony.SmsCbMessage;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
import android.text.format.DateUtils;
-
-import com.android.internal.telephony.gsm.SmsCbHeader;
+import android.text.style.StyleSpan;
/**
* Application wrapper for {@link SmsCbMessage}. This is Parcelable so that
@@ -40,51 +44,40 @@ public class CellBroadcastMessage implements Parcelable {
public static final String SMS_CB_MESSAGE_EXTRA =
"com.android.cellbroadcastreceiver.SMS_CB_MESSAGE";
- private final int mGeographicalScope;
- private final int mSerialNumber;
- private final int mMessageCode;
- private final int mMessageIdentifier;
- private final String mLanguageCode;
- private final String mMessageBody;
+ /** Bold style for formatting CMAS headers and unread message items. */
+ static final StyleSpan STYLE_BOLD = new StyleSpan(Typeface.BOLD);
+
+ /** SmsCbMessage. */
+ private final SmsCbMessage mSmsCbMessage;
+
private final long mDeliveryTime;
private boolean mIsRead;
- public CellBroadcastMessage(SmsCbMessage message) {
- mGeographicalScope = message.getGeographicalScope();
- mSerialNumber = message.getUpdateNumber();
- mMessageCode = message.getMessageCode();
- mMessageIdentifier = message.getMessageIdentifier();
- mLanguageCode = message.getLanguageCode();
- mMessageBody = message.getMessageBody();
+ CellBroadcastMessage(SmsCbMessage message) {
+ mSmsCbMessage = message;
mDeliveryTime = System.currentTimeMillis();
mIsRead = false;
}
- private CellBroadcastMessage(int geoScope, int serialNumber,
- int messageCode, int messageId, String languageCode,
- String messageBody, long deliveryTime, boolean isRead) {
- mGeographicalScope = geoScope;
- mSerialNumber = serialNumber;
- mMessageCode = messageCode;
- mMessageIdentifier = messageId;
- mLanguageCode = languageCode;
- mMessageBody = messageBody;
+ private CellBroadcastMessage(SmsCbMessage message, long deliveryTime, boolean isRead) {
+ mSmsCbMessage = message;
mDeliveryTime = deliveryTime;
mIsRead = isRead;
}
+ private CellBroadcastMessage(Parcel in) {
+ mSmsCbMessage = new SmsCbMessage(in);
+ mDeliveryTime = in.readLong();
+ mIsRead = (in.readInt() != 0);
+ }
+
/** Parcelable: no special flags. */
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel out, int flags) {
- out.writeInt(mGeographicalScope);
- out.writeInt(mSerialNumber);
- out.writeInt(mMessageCode);
- out.writeInt(mMessageIdentifier);
- out.writeString(mLanguageCode);
- out.writeString(mMessageBody);
+ mSmsCbMessage.writeToParcel(out, flags);
out.writeLong(mDeliveryTime);
out.writeInt(mIsRead ? 1 : 0);
}
@@ -92,10 +85,7 @@ public class CellBroadcastMessage implements Parcelable {
public static final Parcelable.Creator<CellBroadcastMessage> CREATOR
= new Parcelable.Creator<CellBroadcastMessage>() {
public CellBroadcastMessage createFromParcel(Parcel in) {
- return new CellBroadcastMessage(
- in.readInt(), in.readInt(),
- in.readInt(), in.readInt(), in.readString(),
- in.readString(), in.readLong(), (in.readInt() != 0));
+ return new CellBroadcastMessage(in);
}
public CellBroadcastMessage[] newArray(int size) {
@@ -111,14 +101,75 @@ public class CellBroadcastMessage implements Parcelable {
public static CellBroadcastMessage createFromCursor(Cursor cursor) {
int geoScope = cursor.getInt(CellBroadcastDatabase.COLUMN_GEOGRAPHICAL_SCOPE);
int serialNum = cursor.getInt(CellBroadcastDatabase.COLUMN_SERIAL_NUMBER);
- int messageCode = cursor.getInt(CellBroadcastDatabase.COLUMN_MESSAGE_CODE);
- int messageId = cursor.getInt(CellBroadcastDatabase.COLUMN_MESSAGE_IDENTIFIER);
+ int category = cursor.getInt(CellBroadcastDatabase.COLUMN_SERVICE_CATEGORY);
String language = cursor.getString(CellBroadcastDatabase.COLUMN_LANGUAGE_CODE);
String body = cursor.getString(CellBroadcastDatabase.COLUMN_MESSAGE_BODY);
+ int format = cursor.getInt(CellBroadcastDatabase.COLUMN_MESSAGE_FORMAT);
+ int priority = cursor.getInt(CellBroadcastDatabase.COLUMN_MESSAGE_PRIORITY);
+
+ String plmn = null;
+ if (!cursor.isNull(CellBroadcastDatabase.COLUMN_PLMN)) {
+ plmn = cursor.getString(CellBroadcastDatabase.COLUMN_PLMN);
+ }
+
+ int lac = -1;
+ if (!cursor.isNull(CellBroadcastDatabase.COLUMN_LAC)) {
+ lac = cursor.getInt(CellBroadcastDatabase.COLUMN_LAC);
+ }
+
+ int cid = -1;
+ if (!cursor.isNull(CellBroadcastDatabase.COLUMN_CID)) {
+ cid = cursor.getInt(CellBroadcastDatabase.COLUMN_CID);
+ }
+
+ SmsCbLocation location = new SmsCbLocation(plmn, lac, cid);
+
+ SmsCbEtwsInfo etwsInfo = null;
+ if (!cursor.isNull(CellBroadcastDatabase.COLUMN_ETWS_WARNING_TYPE)) {
+ int warningType = cursor.getInt(CellBroadcastDatabase.COLUMN_ETWS_WARNING_TYPE);
+ etwsInfo = new SmsCbEtwsInfo(warningType, false, false, null);
+ }
+
+ SmsCbCmasInfo cmasInfo = null;
+ if (!cursor.isNull(CellBroadcastDatabase.COLUMN_CMAS_MESSAGE_CLASS)) {
+ int messageClass = cursor.getInt(CellBroadcastDatabase.COLUMN_CMAS_MESSAGE_CLASS);
+
+ int cmasCategory = SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN;
+ if (!cursor.isNull(CellBroadcastDatabase.COLUMN_CMAS_CATEGORY)) {
+ cmasCategory = cursor.getInt(CellBroadcastDatabase.COLUMN_CMAS_CATEGORY);
+ }
+
+ int responseType = SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN;
+ if (!cursor.isNull(CellBroadcastDatabase.COLUMN_CMAS_RESPONSE_TYPE)) {
+ responseType = cursor.getInt(CellBroadcastDatabase.COLUMN_CMAS_RESPONSE_TYPE);
+ }
+
+ int severity = SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN;
+ if (!cursor.isNull(CellBroadcastDatabase.COLUMN_CMAS_SEVERITY)) {
+ severity = cursor.getInt(CellBroadcastDatabase.COLUMN_CMAS_SEVERITY);
+ }
+
+ int urgency = SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN;
+ if (!cursor.isNull(CellBroadcastDatabase.COLUMN_CMAS_URGENCY)) {
+ urgency = cursor.getInt(CellBroadcastDatabase.COLUMN_CMAS_URGENCY);
+ }
+
+ int certainty = SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN;
+ if (!cursor.isNull(CellBroadcastDatabase.COLUMN_CMAS_CERTAINTY)) {
+ certainty = cursor.getInt(CellBroadcastDatabase.COLUMN_CMAS_CERTAINTY);
+ }
+
+ cmasInfo = new SmsCbCmasInfo(messageClass, cmasCategory, responseType, severity,
+ urgency, certainty);
+ }
+
+ SmsCbMessage msg = new SmsCbMessage(format, geoScope, serialNum, location, category,
+ language, body, priority, etwsInfo, cmasInfo);
+
long deliveryTime = cursor.getLong(CellBroadcastDatabase.COLUMN_DELIVERY_TIME);
boolean isRead = (cursor.getInt(CellBroadcastDatabase.COLUMN_MESSAGE_READ) != 0);
- return new CellBroadcastMessage(geoScope, serialNum, messageCode, messageId,
- language, body, deliveryTime, isRead);
+
+ return new CellBroadcastMessage(msg, deliveryTime, isRead);
}
/**
@@ -126,15 +177,43 @@ public class CellBroadcastMessage implements Parcelable {
* @return a new ContentValues object containing this object's data
*/
public ContentValues getContentValues() {
- ContentValues cv = new ContentValues(8);
- cv.put(CellBroadcastDatabase.Columns.GEOGRAPHICAL_SCOPE, mGeographicalScope);
- cv.put(CellBroadcastDatabase.Columns.SERIAL_NUMBER, mSerialNumber);
- cv.put(CellBroadcastDatabase.Columns.MESSAGE_CODE, mMessageCode);
- cv.put(CellBroadcastDatabase.Columns.MESSAGE_IDENTIFIER, mMessageIdentifier);
- cv.put(CellBroadcastDatabase.Columns.LANGUAGE_CODE, mLanguageCode);
- cv.put(CellBroadcastDatabase.Columns.MESSAGE_BODY, mMessageBody);
+ ContentValues cv = new ContentValues(16);
+ SmsCbMessage msg = mSmsCbMessage;
+ cv.put(CellBroadcastDatabase.Columns.GEOGRAPHICAL_SCOPE, msg.getGeographicalScope());
+ SmsCbLocation location = msg.getLocation();
+ if (location.getPlmn() != null) {
+ cv.put(CellBroadcastDatabase.Columns.PLMN, location.getPlmn());
+ }
+ if (location.getLac() != -1) {
+ cv.put(CellBroadcastDatabase.Columns.LAC, location.getLac());
+ }
+ if (location.getCid() != -1) {
+ cv.put(CellBroadcastDatabase.Columns.CID, location.getCid());
+ }
+ cv.put(CellBroadcastDatabase.Columns.SERIAL_NUMBER, msg.getSerialNumber());
+ cv.put(CellBroadcastDatabase.Columns.SERVICE_CATEGORY, msg.getServiceCategory());
+ cv.put(CellBroadcastDatabase.Columns.LANGUAGE_CODE, msg.getLanguageCode());
+ cv.put(CellBroadcastDatabase.Columns.MESSAGE_BODY, msg.getMessageBody());
cv.put(CellBroadcastDatabase.Columns.DELIVERY_TIME, mDeliveryTime);
cv.put(CellBroadcastDatabase.Columns.MESSAGE_READ, mIsRead);
+ cv.put(CellBroadcastDatabase.Columns.MESSAGE_FORMAT, msg.getMessageFormat());
+ cv.put(CellBroadcastDatabase.Columns.MESSAGE_PRIORITY, msg.getMessagePriority());
+
+ SmsCbEtwsInfo etwsInfo = mSmsCbMessage.getEtwsWarningInfo();
+ if (etwsInfo != null) {
+ cv.put(CellBroadcastDatabase.Columns.ETWS_WARNING_TYPE, etwsInfo.getWarningType());
+ }
+
+ SmsCbCmasInfo cmasInfo = mSmsCbMessage.getCmasWarningInfo();
+ if (cmasInfo != null) {
+ cv.put(CellBroadcastDatabase.Columns.CMAS_MESSAGE_CLASS, cmasInfo.getMessageClass());
+ cv.put(CellBroadcastDatabase.Columns.CMAS_CATEGORY, cmasInfo.getCategory());
+ cv.put(CellBroadcastDatabase.Columns.CMAS_RESPONSE_TYPE, cmasInfo.getResponseType());
+ cv.put(CellBroadcastDatabase.Columns.CMAS_SEVERITY, cmasInfo.getSeverity());
+ cv.put(CellBroadcastDatabase.Columns.CMAS_URGENCY, cmasInfo.getUrgency());
+ cv.put(CellBroadcastDatabase.Columns.CMAS_CERTAINTY, cmasInfo.getCertainty());
+ }
+
return cv;
}
@@ -146,36 +225,221 @@ public class CellBroadcastMessage implements Parcelable {
mIsRead = isRead;
}
- public int getGeographicalScope() {
- return mGeographicalScope;
+ public String getLanguageCode() {
+ return mSmsCbMessage.getLanguageCode();
+ }
+
+ public int getServiceCategory() {
+ return mSmsCbMessage.getServiceCategory();
+ }
+
+ public long getDeliveryTime() {
+ return mDeliveryTime;
+ }
+
+ public String getMessageBody() {
+ return mSmsCbMessage.getMessageBody();
+ }
+
+ public boolean isRead() {
+ return mIsRead;
}
public int getSerialNumber() {
- return mSerialNumber;
+ return mSmsCbMessage.getSerialNumber();
}
- public int getMessageCode() {
- return mMessageCode;
+ /**
+ * Returns a styled CharSequence containing the message body and optional CMAS alert headers.
+ * @param context a Context for resource string access
+ * @return a CharSequence for display in the broadcast alert dialog
+ */
+ public CharSequence getFormattedMessageBody(Context context) {
+ if (isCmasMessage()) {
+ SmsCbCmasInfo cmasInfo = mSmsCbMessage.getCmasWarningInfo();
+ SpannableStringBuilder buf = new SpannableStringBuilder();
+
+ // CMAS category
+ int categoryId = getCmasCategoryResId(cmasInfo);
+ if (categoryId != 0) {
+ buf.append(context.getText(R.string.cmas_category_heading));
+ buf.append(context.getText(categoryId));
+ buf.append('\n');
+ }
+
+ // CMAS response type
+ int responseId = getCmasResponseResId(cmasInfo);
+ if (responseId != 0) {
+ buf.append(context.getText(R.string.cmas_response_heading));
+ buf.append(context.getText(responseId));
+ buf.append('\n');
+ }
+
+ // CMAS severity
+ int severityId = getCmasSeverityResId(cmasInfo);
+ if (severityId != 0) {
+ buf.append(context.getText(R.string.cmas_severity_heading));
+ buf.append(context.getText(severityId));
+ buf.append('\n');
+ }
+
+ // CMAS urgency
+ int urgencyId = getCmasUrgencyResId(cmasInfo);
+ if (urgencyId != 0) {
+ buf.append(context.getText(R.string.cmas_urgency_heading));
+ buf.append(context.getText(urgencyId));
+ buf.append('\n');
+ }
+
+ // CMAS certainty
+ int certaintyId = getCmasCertaintyResId(cmasInfo);
+ if (certaintyId != 0) {
+ buf.append(context.getText(R.string.cmas_certainty_heading));
+ buf.append(context.getText(certaintyId));
+ buf.append('\n');
+ }
+
+ // Style all headings in bold
+ buf.setSpan(STYLE_BOLD, 0, buf.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
+
+ buf.append(mSmsCbMessage.getMessageBody());
+ return buf;
+ } else {
+ return mSmsCbMessage.getMessageBody();
+ }
}
- public int getMessageIdentifier() {
- return mMessageIdentifier;
+ /**
+ * Returns the string resource ID for the CMAS category.
+ * @return a string resource ID, or 0 if the CMAS category is unknown or not present
+ */
+ private static int getCmasCategoryResId(SmsCbCmasInfo cmasInfo) {
+ switch (cmasInfo.getCategory()) {
+ case SmsCbCmasInfo.CMAS_CATEGORY_GEO:
+ return R.string.cmas_category_geo;
+
+ case SmsCbCmasInfo.CMAS_CATEGORY_MET:
+ return R.string.cmas_category_met;
+
+ case SmsCbCmasInfo.CMAS_CATEGORY_SAFETY:
+ return R.string.cmas_category_safety;
+
+ case SmsCbCmasInfo.CMAS_CATEGORY_SECURITY:
+ return R.string.cmas_category_security;
+
+ case SmsCbCmasInfo.CMAS_CATEGORY_RESCUE:
+ return R.string.cmas_category_rescue;
+
+ case SmsCbCmasInfo.CMAS_CATEGORY_FIRE:
+ return R.string.cmas_category_fire;
+
+ case SmsCbCmasInfo.CMAS_CATEGORY_HEALTH:
+ return R.string.cmas_category_health;
+
+ case SmsCbCmasInfo.CMAS_CATEGORY_ENV:
+ return R.string.cmas_category_env;
+
+ case SmsCbCmasInfo.CMAS_CATEGORY_TRANSPORT:
+ return R.string.cmas_category_transport;
+
+ case SmsCbCmasInfo.CMAS_CATEGORY_INFRA:
+ return R.string.cmas_category_infra;
+
+ case SmsCbCmasInfo.CMAS_CATEGORY_CBRNE:
+ return R.string.cmas_category_cbrne;
+
+ case SmsCbCmasInfo.CMAS_CATEGORY_OTHER:
+ return R.string.cmas_category_other;
+
+ default:
+ return 0;
+ }
}
- public String getLanguageCode() {
- return mLanguageCode;
+ /**
+ * Returns the string resource ID for the CMAS response type.
+ * @return a string resource ID, or 0 if the CMAS response type is unknown or not present
+ */
+ private static int getCmasResponseResId(SmsCbCmasInfo cmasInfo) {
+ switch (cmasInfo.getResponseType()) {
+ case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_SHELTER:
+ return R.string.cmas_response_shelter;
+
+ case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_EVACUATE:
+ return R.string.cmas_response_evacuate;
+
+ case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_PREPARE:
+ return R.string.cmas_response_prepare;
+
+ case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_EXECUTE:
+ return R.string.cmas_response_execute;
+
+ case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_MONITOR:
+ return R.string.cmas_response_monitor;
+
+ case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_AVOID:
+ return R.string.cmas_response_avoid;
+
+ case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_ASSESS:
+ return R.string.cmas_response_assess;
+
+ case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_NONE:
+ return R.string.cmas_response_none;
+
+ default:
+ return 0;
+ }
}
- public long getDeliveryTime() {
- return mDeliveryTime;
+ /**
+ * Returns the string resource ID for the CMAS severity.
+ * @return a string resource ID, or 0 if the CMAS severity is unknown or not present
+ */
+ private static int getCmasSeverityResId(SmsCbCmasInfo cmasInfo) {
+ switch (cmasInfo.getSeverity()) {
+ case SmsCbCmasInfo.CMAS_SEVERITY_EXTREME:
+ return R.string.cmas_severity_extreme;
+
+ case SmsCbCmasInfo.CMAS_SEVERITY_SEVERE:
+ return R.string.cmas_severity_severe;
+
+ default:
+ return 0;
+ }
}
- public String getMessageBody() {
- return mMessageBody;
+ /**
+ * Returns the string resource ID for the CMAS urgency.
+ * @return a string resource ID, or 0 if the CMAS urgency is unknown or not present
+ */
+ private static int getCmasUrgencyResId(SmsCbCmasInfo cmasInfo) {
+ switch (cmasInfo.getUrgency()) {
+ case SmsCbCmasInfo.CMAS_URGENCY_IMMEDIATE:
+ return R.string.cmas_urgency_immediate;
+
+ case SmsCbCmasInfo.CMAS_URGENCY_EXPECTED:
+ return R.string.cmas_urgency_expected;
+
+ default:
+ return 0;
+ }
}
- public boolean isRead() {
- return mIsRead;
+ /**
+ * Returns the string resource ID for the CMAS certainty.
+ * @return a string resource ID, or 0 if the CMAS certainty is unknown or not present
+ */
+ private static int getCmasCertaintyResId(SmsCbCmasInfo cmasInfo) {
+ switch (cmasInfo.getCertainty()) {
+ case SmsCbCmasInfo.CMAS_CERTAINTY_OBSERVED:
+ return R.string.cmas_certainty_observed;
+
+ case SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY:
+ return R.string.cmas_certainty_likely;
+
+ default:
+ return 0;
+ }
}
/**
@@ -188,7 +452,7 @@ public class CellBroadcastMessage implements Parcelable {
* @return true if the message is PWS type; false otherwise
*/
public boolean isPublicAlertMessage() {
- return SmsCbHeader.isEmergencyMessage(mMessageIdentifier);
+ return mSmsCbMessage.isEmergencyMessage();
}
/**
@@ -198,9 +462,15 @@ public class CellBroadcastMessage implements Parcelable {
* @return true if the message is PWS type, excluding Amber alerts
*/
public boolean isEmergencyAlertMessage() {
- int id = mMessageIdentifier;
- return SmsCbHeader.isEmergencyMessage(id) &&
- id != SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY;
+ if (!mSmsCbMessage.isEmergencyMessage()) {
+ return false;
+ }
+ SmsCbCmasInfo cmasInfo = mSmsCbMessage.getCmasWarningInfo();
+ if (cmasInfo != null &&
+ cmasInfo.getMessageClass() == SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY) {
+ return false;
+ }
+ return true;
}
/**
@@ -208,7 +478,7 @@ public class CellBroadcastMessage implements Parcelable {
* @return true if the message is ETWS emergency type; false otherwise
*/
public boolean isEtwsMessage() {
- return SmsCbHeader.isEtwsMessage(mMessageIdentifier);
+ return mSmsCbMessage.isEtwsMessage();
}
/**
@@ -216,7 +486,20 @@ public class CellBroadcastMessage implements Parcelable {
* @return true if the message is CMAS emergency type; false otherwise
*/
public boolean isCmasMessage() {
- return SmsCbHeader.isCmasMessage(mMessageIdentifier);
+ return mSmsCbMessage.isCmasMessage();
+ }
+
+ /**
+ * Return the CMAS message class.
+ * @return the CMAS message class, e.g. {@link SmsCbCmasInfo#CMAS_CLASS_SEVERE_THREAT}, or
+ * {@link SmsCbCmasInfo#CMAS_CLASS_UNKNOWN} if this is not a CMAS alert
+ */
+ public int getCmasMessageClass() {
+ if (mSmsCbMessage.isCmasMessage()) {
+ return mSmsCbMessage.getCmasWarningInfo().getMessageClass();
+ } else {
+ return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN;
+ }
}
/**
@@ -225,8 +508,8 @@ public class CellBroadcastMessage implements Parcelable {
* @return true if the message indicates an ETWS popup alert
*/
public boolean isEtwsPopupAlert() {
- return SmsCbHeader.isEtwsMessage(mMessageIdentifier) &&
- SmsCbHeader.isEtwsPopupAlert(mMessageCode);
+ SmsCbEtwsInfo etwsInfo = mSmsCbMessage.getEtwsWarningInfo();
+ return etwsInfo != null && etwsInfo.isPopupAlert();
}
/**
@@ -235,62 +518,77 @@ public class CellBroadcastMessage implements Parcelable {
* @return true if the message indicates an ETWS emergency user alert
*/
public boolean isEtwsEmergencyUserAlert() {
- return SmsCbHeader.isEtwsMessage(mMessageIdentifier) &&
- SmsCbHeader.isEtwsEmergencyUserAlert(mMessageCode);
+ SmsCbEtwsInfo etwsInfo = mSmsCbMessage.getEtwsWarningInfo();
+ return etwsInfo != null && etwsInfo.isEmergencyUserAlert();
}
- public int getDialogTitleResource() {
- switch (mMessageIdentifier) {
- case SmsCbConstants.MESSAGE_ID_ETWS_EARTHQUAKE_WARNING:
- return R.string.etws_earthquake_warning;
-
- case SmsCbConstants.MESSAGE_ID_ETWS_TSUNAMI_WARNING:
- return R.string.etws_tsunami_warning;
-
- case SmsCbConstants.MESSAGE_ID_ETWS_EARTHQUAKE_AND_TSUNAMI_WARNING:
- return R.string.etws_earthquake_and_tsunami_warning;
-
- case SmsCbConstants.MESSAGE_ID_ETWS_TEST_MESSAGE:
- return R.string.etws_test_message;
+ /**
+ * Return whether the broadcast is an ETWS test message.
+ * @return true if the message is an ETWS test message; false otherwise
+ */
+ public boolean isEtwsTestMessage() {
+ SmsCbEtwsInfo etwsInfo = mSmsCbMessage.getEtwsWarningInfo();
+ return etwsInfo != null &&
+ etwsInfo.getWarningType() == SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE;
+ }
- case SmsCbConstants.MESSAGE_ID_ETWS_OTHER_EMERGENCY_TYPE:
- return R.string.etws_other_emergency_type;
+ public int getDialogTitleResource() {
+ // ETWS warning types
+ SmsCbEtwsInfo etwsInfo = mSmsCbMessage.getEtwsWarningInfo();
+ if (etwsInfo != null) {
+ switch (etwsInfo.getWarningType()) {
+ case SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE:
+ return R.string.etws_earthquake_warning;
+
+ case SmsCbEtwsInfo.ETWS_WARNING_TYPE_TSUNAMI:
+ return R.string.etws_tsunami_warning;
+
+ case SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI:
+ return R.string.etws_earthquake_and_tsunami_warning;
+
+ case SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE:
+ return R.string.etws_test_message;
+
+ case SmsCbEtwsInfo.ETWS_WARNING_TYPE_OTHER_EMERGENCY:
+ default:
+ return R.string.etws_other_emergency_type;
+ }
+ }
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL:
- return R.string.cmas_presidential_level_alert;
+ // CMAS warning types
+ SmsCbCmasInfo cmasInfo = mSmsCbMessage.getCmasWarningInfo();
+ if (cmasInfo != null) {
+ switch (cmasInfo.getMessageClass()) {
+ case SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT:
+ return R.string.cmas_presidential_level_alert;
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
- return R.string.cmas_extreme_alert;
+ case SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT:
+ return R.string.cmas_extreme_alert;
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
- return R.string.cmas_severe_alert;
+ case SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT:
+ return R.string.cmas_severe_alert;
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY:
- return R.string.cmas_amber_alert;
+ case SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY:
+ return R.string.cmas_amber_alert;
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST:
- return R.string.cmas_required_monthly_test;
+ case SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST:
+ return R.string.cmas_required_monthly_test;
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXERCISE:
- return R.string.cmas_exercise_alert;
+ case SmsCbCmasInfo.CMAS_CLASS_CMAS_EXERCISE:
+ return R.string.cmas_exercise_alert;
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE:
- return R.string.cmas_operator_defined_alert;
+ case SmsCbCmasInfo.CMAS_CLASS_OPERATOR_DEFINED_USE:
+ return R.string.cmas_operator_defined_alert;
- default:
- if (SmsCbHeader.isEmergencyMessage(mMessageIdentifier) ||
- CellBroadcastConfigService.isOperatorDefinedEmergencyId(
- mMessageIdentifier)) {
+ default:
return R.string.pws_other_message_identifiers;
- } else {
- return R.string.cb_other_message_identifiers;
- }
+ }
+ }
+
+ if (mSmsCbMessage.isEmergencyMessage()) {
+ return R.string.pws_other_message_identifiers;
+ } else {
+ return R.string.cb_other_message_identifiers;
}
}
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastReceiver.java b/src/com/android/cellbroadcastreceiver/CellBroadcastReceiver.java
index bce3afde4..164cd8fce 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastReceiver.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastReceiver.java
@@ -19,17 +19,21 @@ package com.android.cellbroadcastreceiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.preference.PreferenceManager;
import android.provider.Telephony;
import android.telephony.TelephonyManager;
+import android.telephony.cdma.CdmaSmsCbProgramData;
import android.util.Log;
import com.android.internal.telephony.ITelephony;
+import com.android.internal.telephony.cdma.sms.SmsEnvelope;
public class CellBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "CellBroadcastReceiver";
- static final boolean DBG = true; // TODO: change to false before ship
+ static final boolean DBG = true; // STOPSHIP: change to false before ship
@Override
public void onReceive(Context context, Intent intent) {
@@ -60,12 +64,93 @@ public class CellBroadcastReceiver extends BroadcastReceiver {
} else {
Log.e(TAG, "ignoring unprivileged action received " + action);
}
+ } else if (Telephony.Sms.Intents.SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION
+ .equals(action)) {
+ if (privileged) {
+ CdmaSmsCbProgramData[] programDataList = (CdmaSmsCbProgramData[])
+ intent.getParcelableArrayExtra("program_data_list");
+ if (programDataList != null) {
+ handleCdmaSmsCbProgramData(context, programDataList);
+ } else {
+ Log.e(TAG, "SCPD intent received with no program_data_list");
+ }
+ } else {
+ Log.e(TAG, "ignoring unprivileged action received " + action);
+ }
} else {
Log.w(TAG, "onReceive() unexpected action " + action);
}
}
/**
+ * Handle Service Category Program Data message.
+ * TODO: Send Service Category Program Results response message to sender
+ *
+ * @param context
+ * @param programDataList
+ */
+ private void handleCdmaSmsCbProgramData(Context context,
+ CdmaSmsCbProgramData[] programDataList) {
+ for (CdmaSmsCbProgramData programData : programDataList) {
+ switch (programData.getOperation()) {
+ case CdmaSmsCbProgramData.OPERATION_ADD_CATEGORY:
+ tryCdmaSetCategory(context, programData.getCategory(), true);
+ break;
+
+ case CdmaSmsCbProgramData.OPERATION_DELETE_CATEGORY:
+ tryCdmaSetCategory(context, programData.getCategory(), false);
+ break;
+
+ case CdmaSmsCbProgramData.OPERATION_CLEAR_CATEGORIES:
+ tryCdmaSetCategory(context,
+ SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT, false);
+ tryCdmaSetCategory(context,
+ SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT, false);
+ tryCdmaSetCategory(context,
+ SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY, false);
+ tryCdmaSetCategory(context,
+ SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE, false);
+ break;
+
+ default:
+ Log.e(TAG, "Ignoring unknown SCPD operation " + programData.getOperation());
+ }
+ }
+ }
+
+ private void tryCdmaSetCategory(Context context, int category, boolean enable) {
+ SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
+
+ switch (category) {
+ case SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT:
+ sharedPrefs.edit().putBoolean(
+ CellBroadcastSettings.KEY_ENABLE_CMAS_EXTREME_THREAT_ALERTS, enable)
+ .apply();
+ break;
+
+ case SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT:
+ sharedPrefs.edit().putBoolean(
+ CellBroadcastSettings.KEY_ENABLE_CMAS_SEVERE_THREAT_ALERTS, enable)
+ .apply();
+ break;
+
+ case SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY:
+ sharedPrefs.edit().putBoolean(
+ CellBroadcastSettings.KEY_ENABLE_CMAS_AMBER_ALERTS, enable).apply();
+ break;
+
+ case SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE:
+ sharedPrefs.edit().putBoolean(
+ CellBroadcastSettings.KEY_ENABLE_CMAS_TEST_ALERTS, enable).apply();
+ break;
+
+ default:
+ Log.w(TAG, "Ignoring SCPD command to " + (enable ? "enable" : "disable")
+ + " alerts in category " + category);
+ }
+ }
+
+ /**
* Tell {@link CellBroadcastConfigService} to enable the CB channels.
* @param context the broadcast receiver context
*/
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastReceiverApp.java b/src/com/android/cellbroadcastreceiver/CellBroadcastReceiverApp.java
index 6fa593522..9a9b3b7e2 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastReceiverApp.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastReceiverApp.java
@@ -17,50 +17,48 @@
package com.android.cellbroadcastreceiver;
import android.app.Application;
-import android.content.SharedPreferences;
import android.util.Log;
import android.preference.PreferenceManager;
-import static com.android.cellbroadcastreceiver.CellBroadcastReceiver.DBG;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* The application class loads the default preferences at first start,
* and remembers the time of the most recently received broadcast.
*/
public class CellBroadcastReceiverApp extends Application {
- public static final String LOG_TAG = "CellBroadcastReceiverApp";
- public static final String PREF_KEY_NOTIFICATION_ID = "notification_id";
-
- static CellBroadcastReceiverApp gCellBroadcastReceiverApp;
+ private static final String LOG_TAG = "CellBroadcastReceiverApp";
@Override
public void onCreate() {
super.onCreate();
+ // TODO: fix strict mode violation from the following method call during app creation
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
- gCellBroadcastReceiverApp = this;
}
- public static CellBroadcastReceiverApp getCellBroadcastReceiverApp() {
- return gCellBroadcastReceiverApp;
+ /** Number of unread non-emergency alerts since the device was booted. */
+ private static AtomicInteger sUnreadAlertCount = new AtomicInteger();
+
+ /**
+ * Increments the number of unread non-emergency alerts, returning the new value.
+ * @return the updated number of unread non-emergency alerts, after incrementing
+ */
+ static int incrementUnreadAlertCount() {
+ return sUnreadAlertCount.incrementAndGet();
}
- // Each incoming CB gets its own notification. We have to use a new unique notification id
- // for each one.
- public int getNextNotificationId() {
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
- int notificationId = prefs.getInt(PREF_KEY_NOTIFICATION_ID, 0);
- ++notificationId;
- if (notificationId > 32765) {
- notificationId = 1; // wrap around before it gets dangerous
+ /**
+ * Decrements the number of unread non-emergency alerts after the user reads it.
+ */
+ static void decrementUnreadAlertCount() {
+ if (sUnreadAlertCount.decrementAndGet() < 0) {
+ Log.e("CellBroadcastReceiverApp", "mUnreadAlertCount < 0, resetting to 0");
+ sUnreadAlertCount.set(0);
}
+ }
- // Save the updated notificationId in SharedPreferences
- SharedPreferences.Editor editor = prefs.edit();
- editor.putInt(PREF_KEY_NOTIFICATION_ID, notificationId);
- editor.apply();
-
- if (DBG) Log.d(LOG_TAG, "getNextNotificationId: " + notificationId);
-
- return notificationId;
+ /** Resets the unread alert count to zero after user deletes all alerts. */
+ static void resetUnreadAlertCount() {
+ sUnreadAlertCount.set(0);
}
}
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastSettings.java b/src/com/android/cellbroadcastreceiver/CellBroadcastSettings.java
index fa5a742c8..6a5d9b06e 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastSettings.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastSettings.java
@@ -21,6 +21,9 @@ import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceScreen;
+import android.provider.Settings;
/**
* Settings activity for the cell broadcast receiver.
@@ -39,22 +42,26 @@ public class CellBroadcastSettings extends PreferenceActivity {
// Speak contents of alert after playing the alert sound.
public static final String KEY_ENABLE_ALERT_SPEECH = "enable_alert_speech";
- // Preference category for ETWS related settings.
- public static final String KEY_CATEGORY_ETWS_SETTINGS = "category_etws_settings";
-
- // Whether to display ETWS test messages (default is disabled).
- public static final String KEY_ENABLE_ETWS_TEST_ALERTS = "enable_etws_test_alerts";
-
// Preference category for CMAS related settings.
public static final String KEY_CATEGORY_CMAS_SETTINGS = "category_cmas_settings";
- // Whether to display CMAS imminent threat notifications (default is enabled).
- public static final String KEY_ENABLE_CMAS_IMMINENT_THREAT_ALERTS =
- "enable_cmas_imminent_threat_alerts";
+ // Whether to display CMAS extreme threat notifications (default is enabled).
+ public static final String KEY_ENABLE_CMAS_EXTREME_THREAT_ALERTS =
+ "enable_cmas_extreme_threat_alerts";
+
+ // Whether to display CMAS severe threat notifications (default is enabled).
+ public static final String KEY_ENABLE_CMAS_SEVERE_THREAT_ALERTS =
+ "enable_cmas_severe_threat_alerts";
- // Whether to display CMAS amber alert messages (default is disabled).
+ // Whether to display CMAS amber alert messages (default is enabled).
public static final String KEY_ENABLE_CMAS_AMBER_ALERTS = "enable_cmas_amber_alerts";
+ // Preference category for development settings (enabled by settings developer options toggle).
+ public static final String KEY_CATEGORY_DEV_SETTINGS = "category_dev_settings";
+
+ // Whether to display ETWS test messages (default is disabled).
+ public static final String KEY_ENABLE_ETWS_TEST_ALERTS = "enable_etws_test_alerts";
+
// Whether to display CMAS monthly test messages (default is disabled).
public static final String KEY_ENABLE_CMAS_TEST_ALERTS = "enable_cmas_test_alerts";
@@ -68,43 +75,75 @@ public class CellBroadcastSettings extends PreferenceActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- addPreferencesFromResource(R.xml.preferences);
- Resources res = getResources();
- if (!res.getBoolean(R.bool.show_etws_settings)) {
- getPreferenceScreen().removePreference(findPreference(KEY_CATEGORY_ETWS_SETTINGS));
- }
- if (!res.getBoolean(R.bool.show_cmas_settings)) {
- getPreferenceScreen().removePreference(findPreference(KEY_CATEGORY_CMAS_SETTINGS));
- }
- if (!res.getBoolean(R.bool.show_brazil_settings)) {
- getPreferenceScreen().removePreference(findPreference(KEY_CATEGORY_BRAZIL_SETTINGS));
- }
- ListPreference duration = (ListPreference) findPreference(KEY_ALERT_SOUND_DURATION);
- duration.setSummary(duration.getEntry());
- duration.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
- public boolean onPreferenceChange(Preference pref, Object newValue) {
- final ListPreference listPref = (ListPreference) pref;
- final int idx = listPref.findIndexOfValue((String) newValue);
- listPref.setSummary(listPref.getEntries()[idx]);
- return true;
- }
- });
+ // Display the fragment as the main content.
+ getFragmentManager().beginTransaction().replace(android.R.id.content,
+ new CellBroadcastSettingsFragment()).commit();
+ }
- Preference.OnPreferenceChangeListener startConfigServiceListener =
- new Preference.OnPreferenceChangeListener() {
+ /**
+ * New fragment-style implementation of preferences.
+ */
+ public static class CellBroadcastSettingsFragment extends PreferenceFragment {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Load the preferences from an XML resource
+ addPreferencesFromResource(R.xml.preferences);
+
+ PreferenceScreen preferenceScreen = getPreferenceScreen();
+
+ // Handler for settings that require us to reconfigure enabled channels in radio
+ Preference.OnPreferenceChangeListener startConfigServiceListener =
+ new Preference.OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference pref, Object newValue) {
+ CellBroadcastReceiver.startConfigService(pref.getContext());
+ return true;
+ }
+ };
+
+ // Only show debug settings when Developer options is enabled in Settings
+ boolean enableDevSettings = Settings.Secure.getInt(getActivity().getContentResolver(),
+ Settings.Secure.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
+
+ if (enableDevSettings) {
+ // enable/disable all alerts (dev setting)
+ Preference enablePwsAlerts = findPreference(KEY_ENABLE_EMERGENCY_ALERTS);
+ if (enablePwsAlerts != null) {
+ enablePwsAlerts.setOnPreferenceChangeListener(startConfigServiceListener);
+ }
+
+ // alert sound duration (dev setting)
+ ListPreference duration = (ListPreference) findPreference(KEY_ALERT_SOUND_DURATION);
+ duration.setSummary(duration.getEntry());
+ duration.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ @Override
public boolean onPreferenceChange(Preference pref, Object newValue) {
- CellBroadcastReceiver.startConfigService(pref.getContext());
+ final ListPreference listPref = (ListPreference) pref;
+ final int idx = listPref.findIndexOfValue((String) newValue);
+ listPref.setSummary(listPref.getEntries()[idx]);
return true;
}
- };
- Preference enablePwsAlerts = findPreference(KEY_ENABLE_EMERGENCY_ALERTS);
- if (enablePwsAlerts != null) {
- enablePwsAlerts.setOnPreferenceChangeListener(startConfigServiceListener);
- }
- Preference enableChannel50Alerts = findPreference(KEY_ENABLE_CHANNEL_50_ALERTS);
- if (enableChannel50Alerts != null) {
- enableChannel50Alerts.setOnPreferenceChangeListener(startConfigServiceListener);
+ });
+ } else {
+ preferenceScreen.removePreference(findPreference(KEY_CATEGORY_DEV_SETTINGS));
+ }
+
+ Resources res = getResources();
+ if (!res.getBoolean(R.bool.show_cmas_settings)) {
+ preferenceScreen.removePreference(findPreference(KEY_CATEGORY_CMAS_SETTINGS));
+ }
+ if (!res.getBoolean(R.bool.show_brazil_settings)) {
+ preferenceScreen.removePreference(findPreference(KEY_CATEGORY_BRAZIL_SETTINGS));
+ }
+
+ Preference enableChannel50Alerts = findPreference(KEY_ENABLE_CHANNEL_50_ALERTS);
+ if (enableChannel50Alerts != null) {
+ enableChannel50Alerts.setOnPreferenceChangeListener(startConfigServiceListener);
+ }
}
}
}
diff --git a/src/com/android/cellbroadcastreceiver/PrivilegedCellBroadcastReceiver.java b/src/com/android/cellbroadcastreceiver/PrivilegedCellBroadcastReceiver.java
index 5ec97c072..2591c59c3 100644
--- a/src/com/android/cellbroadcastreceiver/PrivilegedCellBroadcastReceiver.java
+++ b/src/com/android/cellbroadcastreceiver/PrivilegedCellBroadcastReceiver.java
@@ -16,7 +16,6 @@
package com.android.cellbroadcastreceiver;
-import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
diff --git a/tests/res/layout/test_buttons.xml b/tests/res/layout/test_buttons.xml
index 5ea2ed115..2dd3849c5 100644
--- a/tests/res/layout/test_buttons.xml
+++ b/tests/res/layout/test_buttons.xml
@@ -25,10 +25,17 @@
android:layout_height="match_parent"
android:orientation="vertical">
+ <CheckBox android:id="@+id/button_delay_broadcast"
+ android:text="@string/button_delay_broadcast"
+ android:layout_marginLeft="20dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <!-- ETWS Alerts -->
<Button android:id="@+id/button_etws_normal_type"
android:text="@string/button_etws_normal_type"
android:layout_marginLeft="20dp"
- android:layout_marginTop="20dp"
+ android:layout_marginTop="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
@@ -44,9 +51,43 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
+ <!-- CDMA CMAS Alerts -->
+ <Button android:id="@+id/button_cmas_pres_alert"
+ android:text="@string/button_cmas_pres_alert"
+ android:layout_marginLeft="20dp"
+ android:layout_marginTop="10dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Button android:id="@+id/button_cmas_extreme_alert"
+ android:text="@string/button_cmas_extreme_alert"
+ android:layout_marginLeft="20dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Button android:id="@+id/button_cmas_severe_alert"
+ android:text="@string/button_cmas_severe_alert"
+ android:layout_marginLeft="20dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Button android:id="@+id/button_cmas_amber_alert"
+ android:text="@string/button_cmas_amber_alert"
+ android:layout_marginLeft="20dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Button android:id="@+id/button_cmas_monthly_test"
+ android:text="@string/button_cmas_monthly_test"
+ android:layout_marginLeft="20dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <!-- Other Alerts -->
<Button android:id="@+id/button_gsm_7bit_type"
android:text="@string/button_gsm_7bit_type"
android:layout_marginLeft="20dp"
+ android:layout_marginTop="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
@@ -86,6 +127,12 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
+ <Button android:id="@+id/button_gsm_7bit_with_language_body_gsm_type"
+ android:text="@string/button_gsm_7bit_with_language_body_gsm_type"
+ android:layout_marginLeft="20dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
<Button android:id="@+id/button_gsm_7bit_with_language_body_umts_type"
android:text="@string/button_gsm_7bit_with_language_body_umts_type"
android:layout_marginLeft="20dp"
@@ -122,12 +169,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
- <CheckBox android:id="@+id/button_delay_broadcast"
- android:text="@string/button_delay_broadcast"
- android:layout_marginLeft="20dp"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
-
</LinearLayout>
</ScrollView>
diff --git a/tests/res/values/strings.xml b/tests/res/values/strings.xml
index 16fbb7b35..5968e1c9d 100644
--- a/tests/res/values/strings.xml
+++ b/tests/res/values/strings.xml
@@ -19,6 +19,11 @@
<string name="button_etws_normal_type">Send ETWS Normal Broadcast</string>
<string name="button_etws_cancel_type">Send ETWS Cancel Broadcast</string>
<string name="button_etws_test_type">Send ETWS Test Broadcast</string>
+ <string name="button_cmas_pres_alert">Send CMAS Presidential Alert</string>
+ <string name="button_cmas_extreme_alert">Send CMAS Extreme Alert</string>
+ <string name="button_cmas_severe_alert">Send CMAS Severe Alert</string>
+ <string name="button_cmas_amber_alert">Send CMAS AMBER Alert</string>
+ <string name="button_cmas_monthly_test">Send CMAS Monthly Test Alert</string>
<string name="button_gsm_7bit_type">Send GSM 7 Bit Test Broadcast</string>
<string name="button_gsm_7bit_umts_type">Send UMTS 7 Bit Test Broadcast</string>
<string name="button_gsm_7bit_nopadding_type">Send GSM 7 Bit Full Length Broadcast</string>
@@ -26,6 +31,7 @@
<string name="button_gsm_7bit_multipage_type">Send GSM 7 Bit Multi Page Broadcast</string>
<string name="button_gsm_7bit_multipage_umts_type">Send UMTS 7 Bit Multi Page Broadcast</string>
<string name="button_gsm_7bit_with_language_type">Send GSM 7 Bit With Language</string>
+ <string name="button_gsm_7bit_with_language_body_gsm_type">Send GSM 7 Bit With Language In Body</string>
<string name="button_gsm_7bit_with_language_body_umts_type">Send UMTS 7 Bit With Language</string>
<string name="button_gsm_ucs2_type">Send GSM UCS-2 Test Broadcast</string>
<string name="button_gsm_ucs2_umts_type">Send UMTS UCS-2 Test Broadcast</string>
diff --git a/tests/src/com/android/cellbroadcastreceiver/DialogSmsDisplayTests.java b/tests/src/com/android/cellbroadcastreceiver/DialogSmsDisplayTests.java
index f7df27adf..095ce0628 100644
--- a/tests/src/com/android/cellbroadcastreceiver/DialogSmsDisplayTests.java
+++ b/tests/src/com/android/cellbroadcastreceiver/DialogSmsDisplayTests.java
@@ -20,12 +20,15 @@ import java.io.UnsupportedEncodingException;
import android.content.Intent;
import android.provider.Telephony.Sms.Intents;
+import android.telephony.SmsCbLocation;
+import android.telephony.SmsCbMessage;
import android.test.ActivityInstrumentationTestCase2;
import android.util.Log;
import com.android.internal.telephony.EncodeException;
import com.android.internal.telephony.GsmAlphabet;
import com.android.internal.telephony.IccUtils;
+import com.android.internal.telephony.gsm.GsmSmsCbMessage;
/**
* Various instrumentation tests for CellBroadcastReceiver.
@@ -106,45 +109,49 @@ public class DialogSmsDisplayTests
}
}
+ private static final SmsCbLocation sEmptyLocation = new SmsCbLocation();
+
+ private static SmsCbMessage createFromPdu(byte[] pdu) {
+ try {
+ byte[][] pdus = new byte[1][];
+ pdus[0] = pdu;
+ return GsmSmsCbMessage.createSmsCbMessage(sEmptyLocation, pdus);
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+ }
+
public void testSendMessage7bit() throws Exception {
Intent intent = new Intent(Intents.SMS_CB_RECEIVED_ACTION);
- byte[][] pdus = new byte[1][];
- pdus[0] = encodeCellBroadcast(0, 0, DCS_7BIT_ENGLISH, "Hello in GSM 7 bit");
- intent.putExtra("pdus", pdus);
+ byte[] pdu = encodeCellBroadcast(0, 0, DCS_7BIT_ENGLISH, "Hello in GSM 7 bit");
+ intent.putExtra("message", createFromPdu(pdu));
getActivity().sendOrderedBroadcast(intent, "android.permission.RECEIVE_SMS");
}
public void testSendMessageUCS2() throws Exception {
Intent intent = new Intent(Intents.SMS_CB_RECEIVED_ACTION);
- byte[][] pdus = new byte[1][];
- pdus[0] = encodeCellBroadcast(0, 0, DCS_16BIT_UCS2, "Hello in UCS2");
- intent.putExtra("pdus", pdus);
+ byte[] pdu = encodeCellBroadcast(0, 0, DCS_16BIT_UCS2, "Hello in UCS2");
+ intent.putExtra("message", createFromPdu(pdu));
getActivity().sendOrderedBroadcast(intent, "android.permission.RECEIVE_SMS");
}
public void testSendEtwsMessageNormal() throws Exception {
Intent intent = new Intent(Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION);
- byte[][] pdus = new byte[1][];
- pdus[0] = etwsMessageNormal;
- intent.putExtra("pdus", pdus);
+ intent.putExtra("message", createFromPdu(etwsMessageNormal));
getActivity().sendOrderedBroadcast(intent,
"android.permission.RECEIVE_EMERGENCY_BROADCAST");
}
public void testSendEtwsMessageCancel() throws Exception {
Intent intent = new Intent(Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION);
- byte[][] pdus = new byte[1][];
- pdus[0] = etwsMessageCancel;
- intent.putExtra("pdus", pdus);
+ intent.putExtra("message", createFromPdu(etwsMessageCancel));
getActivity().sendOrderedBroadcast(intent,
"android.permission.RECEIVE_EMERGENCY_BROADCAST");
}
public void testSendEtwsMessageTest() throws Exception {
Intent intent = new Intent(Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION);
- byte[][] pdus = new byte[1][];
- pdus[0] = etwsMessageTest;
- intent.putExtra("pdus", pdus);
+ intent.putExtra("message", createFromPdu(etwsMessageTest));
getActivity().sendOrderedBroadcast(intent,
"android.permission.RECEIVE_EMERGENCY_BROADCAST");
}
diff --git a/tests/src/com/android/cellbroadcastreceiver/tests/SendCdmaCmasMessages.java b/tests/src/com/android/cellbroadcastreceiver/tests/SendCdmaCmasMessages.java
new file mode 100644
index 000000000..17f76cb41
--- /dev/null
+++ b/tests/src/com/android/cellbroadcastreceiver/tests/SendCdmaCmasMessages.java
@@ -0,0 +1,176 @@
+/*
+ * 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.cellbroadcastreceiver.tests;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Parcel;
+import android.provider.Telephony;
+import android.telephony.SmsCbCmasInfo;
+import android.telephony.SmsCbLocation;
+import android.telephony.SmsCbMessage;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.IccUtils;
+import com.android.internal.telephony.cdma.sms.BearerData;
+import com.android.internal.telephony.cdma.sms.CdmaSmsAddress;
+import com.android.internal.telephony.cdma.sms.SmsEnvelope;
+import com.android.internal.telephony.cdma.sms.UserData;
+import com.android.internal.util.BitwiseOutputStream;
+
+import java.util.Arrays;
+import java.util.Random;
+
+/**
+ * Send some test CDMA CMAS warning notifications.
+ */
+public class SendCdmaCmasMessages {
+
+ private static final String TEST_TEXT = "This is a test CDMA cell broadcast message..."
+ + "678901234567890123456789012345678901234567890";
+
+ private static final String PRES_ALERT =
+ "THE PRESIDENT HAS ISSUED AN EMERGENCY ALERT. CHECK LOCAL MEDIA FOR MORE DETAILS";
+
+ private static final String EXTREME_ALERT = "FLASH FLOOD WARNING FOR SOUTH COCONINO COUNTY"
+ + " - NORTH CENTRAL ARIZONA UNTIL 415 PM MST";
+
+ private static final String SEVERE_ALERT = "SEVERE WEATHER WARNING FOR SOMERSET COUNTY"
+ + " - NEW JERSEY UNTIL 415 PM MST";
+
+ private static final String AMBER_ALERT =
+ "AMBER ALERT:Mountain View,CA VEH'07 Blue Honda Civic CA LIC 5ABC123";
+
+ private static final String MONTHLY_TEST_ALERT = "This is a test of the emergency alert system."
+ + " This is only a test. 89012345678901234567890";
+
+ private static final String IS91_TEXT = "IS91 SHORT MSG"; // max length 14 chars
+
+ public static void testSendCmasPresAlert(Activity activity) {
+ SmsCbMessage cbMessage = createCmasSmsMessage(
+ SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT, 12345, "en",
+ PRES_ALERT, SmsCbCmasInfo.CMAS_CATEGORY_GEO,
+ SmsCbCmasInfo.CMAS_RESPONSE_TYPE_PREPARE, SmsCbCmasInfo.CMAS_SEVERITY_EXTREME,
+ SmsCbCmasInfo.CMAS_URGENCY_EXPECTED, SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY);
+
+ Intent intent = new Intent(Telephony.Sms.Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION);
+ intent.putExtra("message", cbMessage);
+ activity.sendOrderedBroadcast(intent, "android.permission.RECEIVE_SMS");
+ }
+
+ public static void testSendCmasExtremeAlert(Activity activity) {
+ SmsCbMessage cbMessage = createCmasSmsMessage(
+ SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT, 23456, "en",
+ EXTREME_ALERT, SmsCbCmasInfo.CMAS_CATEGORY_MET,
+ SmsCbCmasInfo.CMAS_RESPONSE_TYPE_PREPARE, SmsCbCmasInfo.CMAS_SEVERITY_EXTREME,
+ SmsCbCmasInfo.CMAS_URGENCY_EXPECTED, SmsCbCmasInfo.CMAS_CERTAINTY_OBSERVED);
+
+ Intent intent = new Intent(Telephony.Sms.Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION);
+ intent.putExtra("message", cbMessage);
+ activity.sendOrderedBroadcast(intent, "android.permission.RECEIVE_SMS");
+ }
+
+ public static void testSendCmasSevereAlert(Activity activity) {
+ SmsCbMessage cbMessage = createCmasSmsMessage(
+ SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT, 34567, "en",
+ SEVERE_ALERT, SmsCbCmasInfo.CMAS_CATEGORY_HEALTH,
+ SmsCbCmasInfo.CMAS_RESPONSE_TYPE_AVOID, SmsCbCmasInfo.CMAS_SEVERITY_SEVERE,
+ SmsCbCmasInfo.CMAS_URGENCY_IMMEDIATE, SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY);
+
+ Intent intent = new Intent(Telephony.Sms.Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION);
+ intent.putExtra("message", cbMessage);
+ activity.sendOrderedBroadcast(intent, "android.permission.RECEIVE_SMS");
+ }
+
+ public static void testSendCmasAmberAlert(Activity activity) {
+ SmsCbMessage cbMessage = createCmasSmsMessage(
+ SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY, 45678, "en",
+ AMBER_ALERT, SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN,
+ SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN, SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN,
+ SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN, SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN);
+
+ Intent intent = new Intent(Telephony.Sms.Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION);
+ intent.putExtra("message", cbMessage);
+ activity.sendOrderedBroadcast(intent, "android.permission.RECEIVE_SMS");
+ }
+
+ public static void testSendCmasMonthlyTest(Activity activity) {
+ SmsCbMessage cbMessage = createCmasSmsMessage(
+ SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE, 56789, "en",
+ MONTHLY_TEST_ALERT, SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN,
+ SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN, SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN,
+ SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN, SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN);
+
+ Intent intent = new Intent(Telephony.Sms.Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION);
+ intent.putExtra("message", cbMessage);
+ activity.sendOrderedBroadcast(intent, "android.permission.RECEIVE_SMS");
+ }
+
+ /**
+ * Create a new SmsCbMessage for testing CDMA CMAS support.
+ * @param serviceCategory the CDMA service category
+ * @param messageId the 16-bit message identifier
+ * @param language message language code
+ * @param body message body
+ * @param cmasCategory CMAS category (or -1 to skip adding CMAS type 1 elements record)
+ * @param responseType CMAS response type
+ * @param severity CMAS severity
+ * @param urgency CMAS urgency
+ * @param certainty CMAS certainty
+ * @return the newly created SmsMessage object
+ */
+ private static SmsCbMessage createCmasSmsMessage(int serviceCategory, int messageId,
+ String language, String body, int cmasCategory, int responseType, int severity,
+ int urgency, int certainty) {
+ int cmasMessageClass = serviceCategoryToCmasMessageClass(serviceCategory);
+ SmsCbCmasInfo cmasInfo = new SmsCbCmasInfo(cmasMessageClass, cmasCategory, responseType,
+ severity, urgency, certainty);
+ return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP2,
+ SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE, messageId, new SmsCbLocation("123456"),
+ serviceCategory, language, body, SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, null,
+ cmasInfo);
+ }
+
+ /**
+ * Convert CDMA service category to CMAS message class. Copied from {@code BearerData}.
+ * @param serviceCategory CDMA service category
+ * @return CMAS message class
+ */
+ private static int serviceCategoryToCmasMessageClass(int serviceCategory) {
+ switch (serviceCategory) {
+ case SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT:
+ return SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT;
+
+ case SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT:
+ return SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT;
+
+ case SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT:
+ return SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT;
+
+ case SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY:
+ return SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY;
+
+ case SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE:
+ return SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST;
+
+ default:
+ return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN;
+ }
+ }
+}
diff --git a/tests/src/com/android/cellbroadcastreceiver/tests/SendTestBroadcastActivity.java b/tests/src/com/android/cellbroadcastreceiver/tests/SendTestBroadcastActivity.java
index 7727863e8..0e7310103 100644
--- a/tests/src/com/android/cellbroadcastreceiver/tests/SendTestBroadcastActivity.java
+++ b/tests/src/com/android/cellbroadcastreceiver/tests/SendTestBroadcastActivity.java
@@ -20,7 +20,6 @@ import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
-import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
@@ -102,6 +101,71 @@ public class SendTestBroadcastActivity extends Activity {
}
});
+ /* Send a CMAS presidential alert to app. */
+ Button cmasPresAlertButton = (Button) findViewById(R.id.button_cmas_pres_alert);
+ cmasPresAlertButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ if (mDelayBeforeSending && v != null) {
+ mPendingButtonClick = this;
+ mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+ } else {
+ SendCdmaCmasMessages.testSendCmasPresAlert(SendTestBroadcastActivity.this);
+ }
+ }
+ });
+
+ /* Send a CMAS extreme alert to app. */
+ Button cmasExtremeAlertButton = (Button) findViewById(R.id.button_cmas_extreme_alert);
+ cmasExtremeAlertButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ if (mDelayBeforeSending && v != null) {
+ mPendingButtonClick = this;
+ mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+ } else {
+ SendCdmaCmasMessages.testSendCmasExtremeAlert(SendTestBroadcastActivity.this);
+ }
+ }
+ });
+
+ /* Send a CMAS severe alert to app. */
+ Button cmasSevereAlertButton = (Button) findViewById(R.id.button_cmas_severe_alert);
+ cmasSevereAlertButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ if (mDelayBeforeSending && v != null) {
+ mPendingButtonClick = this;
+ mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+ } else {
+ SendCdmaCmasMessages.testSendCmasSevereAlert(SendTestBroadcastActivity.this);
+ }
+ }
+ });
+
+ /* Send a CMAS AMBER alert to app. */
+ Button cmasAmberAlertButton = (Button) findViewById(R.id.button_cmas_amber_alert);
+ cmasAmberAlertButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ if (mDelayBeforeSending && v != null) {
+ mPendingButtonClick = this;
+ mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+ } else {
+ SendCdmaCmasMessages.testSendCmasAmberAlert(SendTestBroadcastActivity.this);
+ }
+ }
+ });
+
+ /* Send a CMAS monthly test alert to app. */
+ Button cmasMonthlyTestButton = (Button) findViewById(R.id.button_cmas_monthly_test);
+ cmasMonthlyTestButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ if (mDelayBeforeSending && v != null) {
+ mPendingButtonClick = this;
+ mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+ } else {
+ SendCdmaCmasMessages.testSendCmasMonthlyTest(SendTestBroadcastActivity.this);
+ }
+ }
+ });
+
/* Send a GSM 7-bit broadcast message to app. */
Button gsm7bitTypeButton = (Button) findViewById(R.id.button_gsm_7bit_type);
gsm7bitTypeButton.setOnClickListener(new OnClickListener() {
@@ -197,6 +261,21 @@ public class SendTestBroadcastActivity extends Activity {
}
});
+ /* Send a GSM 7-bit broadcast message with language to app. */
+ Button gsm7bitWithLanguageInBodyButton =
+ (Button) findViewById(R.id.button_gsm_7bit_with_language_body_gsm_type);
+ gsm7bitWithLanguageInBodyButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ if (mDelayBeforeSending && v != null) {
+ mPendingButtonClick = this;
+ mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+ } else {
+ SendTestMessages.testSendMessage7bitWithLanguageInBody(
+ SendTestBroadcastActivity.this);
+ }
+ }
+ });
+
/* Send a UMTS 7-bit broadcast message with language to app. */
Button gsm7bitWithLanguageUmtsButton =
(Button) findViewById(R.id.button_gsm_7bit_with_language_body_umts_type);
diff --git a/tests/src/com/android/cellbroadcastreceiver/tests/SendTestMessages.java b/tests/src/com/android/cellbroadcastreceiver/tests/SendTestMessages.java
index 893d03a3e..a8496ff06 100644
--- a/tests/src/com/android/cellbroadcastreceiver/tests/SendTestMessages.java
+++ b/tests/src/com/android/cellbroadcastreceiver/tests/SendTestMessages.java
@@ -16,17 +16,19 @@
package com.android.cellbroadcastreceiver.tests;
-import java.io.UnsupportedEncodingException;
-
import android.app.Activity;
import android.content.Intent;
import android.provider.Telephony.Sms.Intents;
-import android.test.ActivityInstrumentationTestCase2;
+import android.telephony.SmsCbLocation;
+import android.telephony.SmsCbMessage;
import android.util.Log;
import com.android.internal.telephony.EncodeException;
import com.android.internal.telephony.GsmAlphabet;
import com.android.internal.telephony.IccUtils;
+import com.android.internal.telephony.gsm.GsmSmsCbMessage;
+
+import java.io.UnsupportedEncodingException;
/**
* Send test messages.
@@ -381,64 +383,47 @@ public class SendTestMessages {
(byte)0x50
};
- static byte[] encodeCellBroadcast(int serialNumber, int messageId, int dcs, String message) {
- byte[] pdu = new byte[88];
- pdu[0] = (byte) ((serialNumber >> 8) & 0xff);
- pdu[1] = (byte) (serialNumber & 0xff);
- pdu[2] = (byte) ((messageId >> 8) & 0xff);
- pdu[3] = (byte) (messageId & 0xff);
- pdu[4] = (byte) (dcs & 0xff);
- pdu[5] = 0x11; // single page message
+ private static final SmsCbLocation sEmptyLocation = new SmsCbLocation();
+
+ private static SmsCbMessage createFromPdu(byte[] pdu) {
try {
- byte[] encodedString;
- if (dcs == DCS_16BIT_UCS2) {
- encodedString = message.getBytes("UTF-16");
- System.arraycopy(encodedString, 0, pdu, 6, encodedString.length);
- } else {
- // byte 0 of encodedString is the length in septets (don't copy)
- encodedString = GsmAlphabet.stringToGsm7BitPacked(message);
- System.arraycopy(encodedString, 1, pdu, 6, encodedString.length-1);
- }
- return pdu;
- } catch (EncodeException e) {
- Log.e(TAG, "Encode Exception");
+ byte[][] pdus = new byte[1][];
+ pdus[0] = pdu;
+ return GsmSmsCbMessage.createSmsCbMessage(sEmptyLocation, pdus);
+ } catch (IllegalArgumentException e) {
return null;
- } catch (UnsupportedEncodingException e) {
- Log.e(TAG, "Unsupported encoding exception for UTF-16");
+ }
+ }
+
+ private static SmsCbMessage createFromPdus(byte[][] pdus) {
+ try {
+ return GsmSmsCbMessage.createSmsCbMessage(sEmptyLocation, pdus);
+ } catch (IllegalArgumentException e) {
return null;
}
}
public static void testSendMessage7bit(Activity activity) {
Intent intent = new Intent(Intents.SMS_CB_RECEIVED_ACTION);
- byte[][] pdus = new byte[1][];
- pdus[0] = gsm7BitTest;
-// pdus[0] = encodeCellBroadcast(0, 0, DCS_7BIT_ENGLISH, "Hello in GSM 7 bit");
- intent.putExtra("pdus", pdus);
+ intent.putExtra("message", createFromPdu(gsm7BitTest));
activity.sendOrderedBroadcast(intent, "android.permission.RECEIVE_SMS");
}
public static void testSendMessage7bitUmts(Activity activity) {
Intent intent = new Intent(Intents.SMS_CB_RECEIVED_ACTION);
- byte[][] pdus = new byte[1][];
- pdus[0] = gsm7BitTestUmts;
- intent.putExtra("pdus", pdus);
+ intent.putExtra("message", createFromPdu(gsm7BitTestUmts));
activity.sendOrderedBroadcast(intent, "android.permission.RECEIVE_SMS");
}
public static void testSendMessage7bitNoPadding(Activity activity) {
Intent intent = new Intent(Intents.SMS_CB_RECEIVED_ACTION);
- byte[][] pdus = new byte[1][];
- pdus[0] = gsm7BitTestNoPadding;
- intent.putExtra("pdus", pdus);
+ intent.putExtra("message", createFromPdu(gsm7BitTestNoPadding));
activity.sendOrderedBroadcast(intent, "android.permission.RECEIVE_SMS");
}
public static void testSendMessage7bitNoPaddingUmts(Activity activity) {
Intent intent = new Intent(Intents.SMS_CB_RECEIVED_ACTION);
- byte[][] pdus = new byte[1][];
- pdus[0] = gsm7BitTestNoPaddingUmts;
- intent.putExtra("pdus", pdus);
+ intent.putExtra("message", createFromPdu(gsm7BitTestNoPaddingUmts));
activity.sendOrderedBroadcast(intent, "android.permission.RECEIVE_SMS");
}
@@ -447,106 +432,81 @@ public class SendTestMessages {
byte[][] pdus = new byte[2][];
pdus[0] = gsm7BitTestMultipage1;
pdus[1] = gsm7BitTestMultipage2;
- intent.putExtra("pdus", pdus);
+ intent.putExtra("message", createFromPdus(pdus));
activity.sendOrderedBroadcast(intent, "android.permission.RECEIVE_SMS");
}
public static void testSendMessage7bitMultipageUmts(Activity activity) {
Intent intent = new Intent(Intents.SMS_CB_RECEIVED_ACTION);
- byte[][] pdus = new byte[1][];
- pdus[0] = gsm7BitTestMultipageUmts;
- intent.putExtra("pdus", pdus);
+ intent.putExtra("message", createFromPdu(gsm7BitTestMultipageUmts));
activity.sendOrderedBroadcast(intent, "android.permission.RECEIVE_SMS");
}
public static void testSendMessage7bitWithLanguage(Activity activity) {
Intent intent = new Intent(Intents.SMS_CB_RECEIVED_ACTION);
- byte[][] pdus = new byte[1][];
- pdus[0] = gsm7BitTestWithLanguage;
- intent.putExtra("pdus", pdus);
+ intent.putExtra("message", createFromPdu(gsm7BitTestWithLanguage));
activity.sendOrderedBroadcast(intent, "android.permission.RECEIVE_SMS");
}
public static void testSendMessage7bitWithLanguageInBody(Activity activity) {
Intent intent = new Intent(Intents.SMS_CB_RECEIVED_ACTION);
- byte[][] pdus = new byte[1][];
- pdus[0] = gsm7BitTestWithLanguageInBody;
- intent.putExtra("pdus", pdus);
+ intent.putExtra("message", createFromPdu(gsm7BitTestWithLanguageInBody));
activity.sendOrderedBroadcast(intent, "android.permission.RECEIVE_SMS");
}
public static void testSendMessage7bitWithLanguageInBodyUmts(Activity activity) {
Intent intent = new Intent(Intents.SMS_CB_RECEIVED_ACTION);
- byte[][] pdus = new byte[1][];
- pdus[0] = gsm7BitTestWithLanguageInBodyUmts;
- intent.putExtra("pdus", pdus);
+ intent.putExtra("message", createFromPdu(gsm7BitTestWithLanguageInBodyUmts));
activity.sendOrderedBroadcast(intent, "android.permission.RECEIVE_SMS");
}
public static void testSendMessageUcs2(Activity activity) {
Intent intent = new Intent(Intents.SMS_CB_RECEIVED_ACTION);
- byte[][] pdus = new byte[1][];
- pdus[0] = gsmUcs2Test;
-// pdus[0] = encodeCellBroadcast(0, 0, DCS_16BIT_UCS2, "Hello in UCS2");
- intent.putExtra("pdus", pdus);
+ intent.putExtra("message", createFromPdu(gsmUcs2Test));
activity.sendOrderedBroadcast(intent, "android.permission.RECEIVE_SMS");
}
public static void testSendMessageUcs2Umts(Activity activity) {
Intent intent = new Intent(Intents.SMS_CB_RECEIVED_ACTION);
- byte[][] pdus = new byte[1][];
- pdus[0] = gsmUcs2TestUmts;
- intent.putExtra("pdus", pdus);
+ intent.putExtra("message", createFromPdu(gsmUcs2TestUmts));
activity.sendOrderedBroadcast(intent, "android.permission.RECEIVE_SMS");
}
public static void testSendMessageUcs2MultipageUmts(Activity activity) {
Intent intent = new Intent(Intents.SMS_CB_RECEIVED_ACTION);
- byte[][] pdus = new byte[1][];
- pdus[0] = gsmUcs2TestMultipageUmts;
- intent.putExtra("pdus", pdus);
+ intent.putExtra("message", createFromPdu(gsmUcs2TestMultipageUmts));
activity.sendOrderedBroadcast(intent, "android.permission.RECEIVE_SMS");
}
public static void testSendMessageUcs2WithLanguageInBody(Activity activity) {
Intent intent = new Intent(Intents.SMS_CB_RECEIVED_ACTION);
- byte[][] pdus = new byte[1][];
- pdus[0] = gsmUcs2TestWithLanguageInBody;
- intent.putExtra("pdus", pdus);
+ intent.putExtra("message", createFromPdu(gsmUcs2TestWithLanguageInBody));
activity.sendOrderedBroadcast(intent, "android.permission.RECEIVE_SMS");
}
public static void testSendMessageUcs2WithLanguageUmts(Activity activity) {
Intent intent = new Intent(Intents.SMS_CB_RECEIVED_ACTION);
- byte[][] pdus = new byte[1][];
- pdus[0] = gsmUcs2TestWithLanguageInBodyUmts;
- intent.putExtra("pdus", pdus);
+ intent.putExtra("message", createFromPdu(gsmUcs2TestWithLanguageInBodyUmts));
activity.sendOrderedBroadcast(intent, "android.permission.RECEIVE_SMS");
}
public static void testSendEtwsMessageNormal(Activity activity) {
Intent intent = new Intent(Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION);
- byte[][] pdus = new byte[1][];
- pdus[0] = etwsMessageNormal;
- intent.putExtra("pdus", pdus);
+ intent.putExtra("message", createFromPdu(etwsMessageNormal));
activity.sendOrderedBroadcast(intent,
"android.permission.RECEIVE_EMERGENCY_BROADCAST");
}
public static void testSendEtwsMessageCancel(Activity activity) {
Intent intent = new Intent(Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION);
- byte[][] pdus = new byte[1][];
- pdus[0] = etwsMessageCancel;
- intent.putExtra("pdus", pdus);
+ intent.putExtra("message", createFromPdu(etwsMessageCancel));
activity.sendOrderedBroadcast(intent,
"android.permission.RECEIVE_EMERGENCY_BROADCAST");
}
public static void testSendEtwsMessageTest(Activity activity) {
Intent intent = new Intent(Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION);
- byte[][] pdus = new byte[1][];
- pdus[0] = etwsMessageTest;
- intent.putExtra("pdus", pdus);
+ intent.putExtra("message", createFromPdu(etwsMessageTest));
activity.sendOrderedBroadcast(intent,
"android.permission.RECEIVE_EMERGENCY_BROADCAST");
}