Initial checkin of Cell Broadcast display application.
Simple Cell Broadcast application. Supports ETWS and CMAS emergency
messages, and can be extended to support other types of PWS
(Public Warning System) emergency warnings. User preferences for
configuration of each supported national system (ETWS, CMAS, and
channel 50 broadcasts for Brazil) are displayed/hidden based on the
values in res/values/config.xml.
For emergency alerts, a flashing warning icon and special alert sound
is played. If there is a text-to-speech engine installed for the
language of the broadcast, then the contents of the broadcast message
will be spoken after the alert sound is played. The user can disable
the text-to-speech feature in settings, as well as adjusting the
length of the alert sound and enabling/disabling delivery of several
emergency broadcast channels.
The CellBroadcastReceiverTests apk includes a launcher activity with
buttons for sending each type of test message. This APK must be signed
with the system certificate in order to acquire the BROADCAST_SMS
permission required to send test broadcasts to the app.
Change-Id: If9b61e02c246de5783b3e39cd100ea707ea80084
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..5343d1a
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,15 @@
+# Copyright 2011 The Android Open Source Project
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CellBroadcastReceiver
+
+include $(BUILD_PACKAGE)
+
+# This finds and builds the test apk as well, so a single make does both.
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
new file mode 100644
index 0000000..59234db
--- /dev/null
+++ b/AndroidManifest.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * 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.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cellbroadcastreceiver">
+
+ <original-package android:name="com.android.cellbroadcastreceiver" />
+
+ <uses-permission android:name="android.permission.RECEIVE_SMS" />
+ <uses-permission android:name="android.permission.RECEIVE_EMERGENCY_BROADCAST" />
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+ <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+ <uses-permission android:name="android.permission.VIBRATE" />
+
+ <application android:name="CellBroadcastReceiverApp"
+ android:label="@string/app_label"
+ android:icon="@drawable/ic_launcher_cbreceiver">
+
+ <service android:name="CellBroadcastAlertAudio"
+ android:exported="false" />
+
+ <service android:name="CellBroadcastAlertService"
+ android:exported="false" />
+
+ <service android:name="CellBroadcastConfigService"
+ android:exported="false" />
+
+ <service android:name="CellBroadcastDatabaseService"
+ android:exported="false" />
+
+ <activity android:name="CellBroadcastListActivity"
+ android:label="@string/app_label"
+ android:configChanges="orientation|keyboardHidden"
+ android:launchMode="singleTop">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="CellBroadcastSettings"
+ android:label="@string/sms_cb_settings"
+ android:exported="true" />
+
+ <!-- Require sender permissions to prevent SMS spoofing -->
+ <receiver android:name="PrivilegedCellBroadcastReceiver"
+ android:permission="android.permission.BROADCAST_SMS">
+ <intent-filter>
+ <action android:name="android.provider.Telephony.SMS_EMERGENCY_CB_RECEIVED" />
+ </intent-filter>
+
+ <intent-filter>
+ <action android:name="android.provider.Telephony.SMS_CB_RECEIVED" />
+ </intent-filter>
+ </receiver>
+
+ <receiver android:name="CellBroadcastReceiver">
+ <intent-filter>
+ <action android:name="android.intent.action.BOOT_COMPLETED" />
+ </intent-filter>
+
+ <intent-filter>
+ <action android:name="android.intent.action.AIRPLANE_MODE" />
+ </intent-filter>
+ </receiver>
+
+ </application>
+</manifest>
diff --git a/CleanSpec.mk b/CleanSpec.mk
new file mode 100644
index 0000000..b84e1b6
--- /dev/null
+++ b/CleanSpec.mk
@@ -0,0 +1,49 @@
+# 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.
+#
+
+# If you don't need to do a full clean build but would like to touch
+# a file or delete some intermediate files, add a clean step to the end
+# of the list. These steps will only be run once, if they haven't been
+# run before.
+#
+# E.g.:
+# $(call add-clean-step, touch -c external/sqlite/sqlite3.h)
+# $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates)
+#
+# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with
+# files that are missing or have been moved.
+#
+# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory.
+# Use $(OUT_DIR) to refer to the "out" directory.
+#
+# If you need to re-do something that's already mentioned, just copy
+# the command and add it to the bottom of the list. E.g., if a change
+# that you made last week required touching a file and a change you
+# made today requires touching the same file, just copy the old
+# touch step and add it to the end of the list.
+#
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
+
+# For example:
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates)
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates)
+#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
+#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
+
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..c5b1efa
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,190 @@
+
+ Copyright (c) 2005-2008, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+
+ 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.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/res/drawable-hdpi/ic_launcher_cbreceiver.png b/res/drawable-hdpi/ic_launcher_cbreceiver.png
new file mode 100644
index 0000000..7185707
--- /dev/null
+++ b/res/drawable-hdpi/ic_launcher_cbreceiver.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_warning_large.png b/res/drawable-hdpi/ic_warning_large.png
new file mode 100644
index 0000000..95be641
--- /dev/null
+++ b/res/drawable-hdpi/ic_warning_large.png
Binary files differ
diff --git a/res/drawable-hdpi/stat_color_warning.png b/res/drawable-hdpi/stat_color_warning.png
new file mode 100644
index 0000000..65490c6
--- /dev/null
+++ b/res/drawable-hdpi/stat_color_warning.png
Binary files differ
diff --git a/res/drawable-ldpi/ic_launcher_cbreceiver.png b/res/drawable-ldpi/ic_launcher_cbreceiver.png
new file mode 100644
index 0000000..bf13f56
--- /dev/null
+++ b/res/drawable-ldpi/ic_launcher_cbreceiver.png
Binary files differ
diff --git a/res/drawable-ldpi/ic_warning_large.png b/res/drawable-ldpi/ic_warning_large.png
new file mode 100644
index 0000000..a068d04
--- /dev/null
+++ b/res/drawable-ldpi/ic_warning_large.png
Binary files differ
diff --git a/res/drawable-ldpi/stat_color_warning.png b/res/drawable-ldpi/stat_color_warning.png
new file mode 100644
index 0000000..9faa035
--- /dev/null
+++ b/res/drawable-ldpi/stat_color_warning.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_cbreceiver.png b/res/drawable-mdpi/ic_launcher_cbreceiver.png
new file mode 100644
index 0000000..2d685dc
--- /dev/null
+++ b/res/drawable-mdpi/ic_launcher_cbreceiver.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_warning_large.png b/res/drawable-mdpi/ic_warning_large.png
new file mode 100644
index 0000000..4417c3d
--- /dev/null
+++ b/res/drawable-mdpi/ic_warning_large.png
Binary files differ
diff --git a/res/drawable-mdpi/stat_color_warning.png b/res/drawable-mdpi/stat_color_warning.png
new file mode 100644
index 0000000..8dc6ab2
--- /dev/null
+++ b/res/drawable-mdpi/stat_color_warning.png
Binary files differ
diff --git a/res/drawable/list_item_background_read.xml b/res/drawable/list_item_background_read.xml
new file mode 100644
index 0000000..6b84893
--- /dev/null
+++ b/res/drawable/list_item_background_read.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_selected="true"
+ android:drawable="@android:color/transparent" />
+ <item android:state_pressed="true" android:state_selected="false"
+ android:drawable="@android:color/transparent" />
+ <item android:state_selected="false"
+ android:drawable="@color/read_bgcolor" />
+</selector>
diff --git a/res/drawable/list_item_background_unread.xml b/res/drawable/list_item_background_unread.xml
new file mode 100644
index 0000000..ffedbc1
--- /dev/null
+++ b/res/drawable/list_item_background_unread.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_selected="true"
+ android:drawable="@android:color/transparent" />
+ <item android:state_pressed="true" android:state_selected="false"
+ android:drawable="@android:color/transparent" />
+ <item android:state_selected="false"
+ android:drawable="@color/unread_bgcolor" />
+</selector>
diff --git a/res/layout/cell_broadcast_list_item.xml b/res/layout/cell_broadcast_list_item.xml
new file mode 100644
index 0000000..a039aff
--- /dev/null
+++ b/res/layout/cell_broadcast_list_item.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * 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.
+ */
+-->
+
+<com.android.cellbroadcastreceiver.CellBroadcastListItem xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="?android:attr/listPreferredItemHeight"
+ android:background="@drawable/list_item_background_unread"
+ android:paddingRight="10dip" >
+
+ <TextView android:id="@+id/channel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMediumInverse"
+ android:singleLine="true"
+ android:layout_marginTop="6dip"
+ android:layout_marginRight="5dip"
+ android:layout_marginLeft="7dip"
+ android:layout_alignParentTop="true"
+ android:layout_alignWithParentIfMissing="true"
+ android:ellipsize="marquee" />
+
+ <TextView android:id="@+id/date"
+ android:layout_marginTop="6dip"
+ android:layout_marginRight="5dip"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmallInverse"
+ android:singleLine="true"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentTop="true" />
+
+ <TextView android:id="@+id/message"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmallInverse"
+ android:singleLine="true"
+ android:layout_marginBottom="10dip"
+ android:layout_marginLeft="7dip"
+ android:layout_alignParentBottom="true"
+ android:layout_alignWithParentIfMissing="true"
+ android:ellipsize="end" />
+
+</com.android.cellbroadcastreceiver.CellBroadcastListItem>
diff --git a/res/layout/cell_broadcast_list_screen.xml b/res/layout/cell_broadcast_list_screen.xml
new file mode 100644
index 0000000..44de8ee
--- /dev/null
+++ b/res/layout/cell_broadcast_list_screen.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * 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.
+ */
+-->
+
+<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" />
diff --git a/res/layout/delete_broadcast_dialog_view.xml b/res/layout/delete_broadcast_dialog_view.xml
new file mode 100644
index 0000000..0ae4328
--- /dev/null
+++ b/res/layout/delete_broadcast_dialog_view.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="15dip">
+
+ <TextView android:id="@+id/message"
+ style="?android:attr/textAppearanceMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
diff --git a/res/raw/attention_signal.ogg b/res/raw/attention_signal.ogg
new file mode 100644
index 0000000..4b6ae3a
--- /dev/null
+++ b/res/raw/attention_signal.ogg
Binary files differ
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
new file mode 100644
index 0000000..f0c620e
--- /dev/null
+++ b/res/values-ja/strings.xml
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <!-- The name of the Cell Broadcast Receiver app. [CHAR LIMIT=NONE] -->
+ <string name="app_label">"緊急地震速報"</string>
+
+ <!-- Label for settings screen. [CHAR LIMIT=NONE] -->
+ <string name="sms_cb_settings">"緊急地震速報設定"</string>
+
+ <!-- Text for dismiss button in broadcast message view dialog. [CHAR LIMIT=25] -->
+ <string name="button_dismiss">"閉じる"</string>
+
+ <!-- Menu item for accessing application settings. [CHAR LIMIT=30] -->
+ <string name="menu_preferences">"設定"</string>
+ <!-- Menu item for deleting all broadcasts. [CHAR LIMIT=30] -->
+ <string name="menu_delete_all">"通知の削除"</string>
+ <!-- Context menu item to view a previously received broadcast. [CHAR LIMIT=30] -->
+ <string name="menu_view">"通知の表示"</string>
+ <!-- Context menu item to delete a previously received broadcast. [CHAR LIMIT=30] -->
+ <string name="menu_delete">"通知の削除"</string>
+
+ <!-- Confirm Delete -->
+ <!-- Delete confirmation dialog title. [CHAR LIMIT=30] -->
+ <string name="confirm_dialog_title">"削除"</string>
+ <!-- Delete broadcast confirmation dialog message. [CHAR LIMIT=NONE] -->
+ <string name="confirm_delete_broadcast">"通知を削除します"</string>
+ <!-- Delete all broadcasts confirmation dialog message. [CHAR LIMIT=NONE] -->
+ <string name="confirm_delete_all_broadcasts">"受信した全ての通知を削除します"</string>
+ <!-- Delete button text for delete broadcast dialog. [CHAR LIMIT=25] -->
+ <string name="button_delete">"削除"</string>
+ <!-- Cancel button text for delete broadcast dialog. [CHAR LIMIT=25] -->
+ <string name="button_cancel">"キャンセル"</string>
+
+ <!-- ETWS dialog title for Earthquake Warning. [CHAR LIMIT=50] -->
+ <string name="etws_earthquake_warning">"緊急地震速報"</string>
+ <!-- ETWS dialog title for Tsunami Warning. [CHAR LIMIT=50] -->
+ <string name="etws_tsunami_warning">"津波警報"</string>
+ <!-- ETWS dialog title for Earthquake and Tsunami Warning. [CHAR LIMIT=50] -->
+ <string name="etws_earthquake_and_tsunami_warning">"緊急地震速報及び津波警報"</string>
+ <!-- ETWS dialog title for test message. [CHAR LIMIT=50] -->
+ <string name="etws_test_message">"ETWS 試験メッセージ"</string>
+ <!-- ETWS dialog title for other emergency type. [CHAR LIMIT=50] -->
+ <string name="etws_other_emergency_type">"緊急速報"</string>
+ <!-- CMAS dialog title for presidential level alert. [CHAR LIMIT=50] -->
+ <string name="cmas_presidential_level_alert">Presidential Alert</string>
+ <!-- CMAS dialog title for extreme alert. [CHAR LIMIT=50] -->
+ <string name="cmas_extreme_alert">Emergency Alert: Extreme</string>
+ <!-- CMAS dialog title for severe alert. [CHAR LIMIT=50] -->
+ <string name="cmas_severe_alert">Emergency Alert: Severe</string>
+ <!-- CMAS dialog title for child abduction emergency (Amber Alert). [CHAR LIMIT=50] -->
+ <string name="cmas_amber_alert">Child Abduction (Amber Alert)</string>
+ <!-- CMAS dialog title for required monthly test. [CHAR LIMIT=50] -->
+ <string name="cmas_required_monthly_test">Emergency Alert Monthly Test</string>
+ <!-- CMAS dialog title for CMAS Exercise. [CHAR LIMIT=50] -->
+ <string name="cmas_exercise_alert">Emergency Alert (Exercise)</string>
+ <!-- CMAS dialog title for operator defined use. [CHAR LIMIT=50] -->
+ <string name="cmas_operator_defined_alert">Emergency Alert (Operator)</string>
+ <!-- Dialog title for all other message identifiers in the PWS range. [CHAR LIMIT=50] -->
+ <string name="pws_other_message_identifiers">"緊急速報"</string>
+ <!-- Dialog title for all non-emergency cell broadcasts. [CHAR LIMIT=50] -->
+ <string name="cb_other_message_identifiers">"エリアメール"</string>
+
+ <!-- Preference category title for emergency alert settings. [CHAR LIMIT=50] -->
+ <string name="emergency_alert_settings_title">"緊急地震速報設定"</string>
+ <!-- Preference title for enable emergency alerts checkbox. [CHAR LIMIT=50] -->
+ <string name="enable_emergency_alerts_title">"通知を有効"</string>
+ <!-- Preference summary for enable notifications checkbox. [CHAR LIMIT=50] -->
+ <string name="enable_emergency_alerts_summary">"緊急地震速報の表示"</string>
+ <!-- Preference title for alert sound duration list. [CHAR LIMIT=50] -->
+ <string name="alert_sound_duration_title">鳴動時間</string>
+ <!-- Do not translate. Empty summary for alert duration (set by CellBroadcastSettings). -->
+ <string name="alert_sound_duration_summary"></string>
+ <!-- Preference title for enable text-to-speech checkbox. [CHAR LIMIT=50] -->
+ <string name="enable_alert_speech_title">鳴動後の読み上げ</string>
+ <!-- Preference summary for enable text-to-speech checkbox. [CHAR LIMIT=100] -->
+ <string name="enable_alert_speech_summary">読み上げによる緊急地震速報の通知</string>
+ <!-- Preference title for enable ETWS test alerts checkbox. [CHAR LIMIT=50] -->
+ <string name="enable_etws_test_alerts_title">ETWS 試験通知を有効</string>
+ <!-- Preference summary for enable ETWS test alerts checkbox. [CHAR LIMIT=100] -->
+ <string name="enable_etws_test_alerts_summary">地震及び津波警告試験通知の表示</string>
+ <!-- Preference title for enable CMAS amber alerts checkbox. [CHAR LIMIT=50] -->
+ <string name="enable_cmas_amber_alerts_title">CMAS 誘拐事件速報の通知</string>
+ <!-- Preference summary for enable CMAS amber alerts checkbox. [CHAR LIMIT=100] -->
+ <string name="enable_cmas_amber_alerts_summary">誘拐事件速報の表示(緊急小児誘拐)</string>
+ <!-- Preference title for enable CMAS test alerts checkbox. [CHAR LIMIT=50] -->
+ <string name="enable_cmas_test_alerts_title">CMAS 試験通知を有効</string>
+ <!-- Preference summary for enable CMAS test alerts checkbox. [CHAR LIMIT=100] -->
+ <string name="enable_cmas_test_alerts_summary">商用モバイルアラートにおける月ごとの試験通知の表示</string>
+ <!-- Preference title for enable channel 50 alerts (Brazil only). [CHAR LIMIT=50] -->
+ <string name="enable_channel_50_alerts_title">チャンネル50ブロードキャストを有効</string>
+ <!-- Preference summary for enable channel 50 alerts (Brazil only). [CHAR LIMIT=100] -->
+ <string name="enable_channel_50_alerts_summary">チャンネル50は、ブラジルで利用されるエリア更新情報です</string>
+
+ <!-- Entries listed in the ListPreference for allowed alert durations. [CHAR LIMIT=30] -->
+ <string-array name="alert_sound_duration_entries">
+ <item>"2秒"</item>
+ <item>"4秒"</item>
+ <item>"6秒"</item>
+ <item>"8秒"</item>
+ <item>"10秒"</item>
+ </string-array>
+
+ <!-- Do not translate. Values that are retrieved from the ListPreference.
+ These must match the alert_sound_duration_entries above. -->
+ <string-array name="alert_sound_duration_values">
+ <item>2</item>
+ <item>4</item>
+ <item>6</item>
+ <item>8</item>
+ <item>10</item>
+ </string-array>
+</resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
new file mode 100644
index 0000000..2896862
--- /dev/null
+++ b/res/values/colors.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * 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.
+ */
+-->
+<resources>
+ <!-- the background color used for unread broadcasts -->
+ <color name="unread_bgcolor">#ffffffff</color>
+ <color name="read_bgcolor">#ffeeeeee</color>
+</resources>
+
diff --git a/res/values/config.xml b/res/values/config.xml
new file mode 100644
index 0000000..02311a9
--- /dev/null
+++ b/res/values/config.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+ <!-- Whether to enable ETWS settings (Japan) -->
+ <bool name="show_etws_settings">true</bool>
+ <!-- Whether to enable CMAS settings (United States) -->
+ <bool name="show_cmas_settings">true</bool>
+ <!-- Whether to enable channel 50 settings (Brazil) -->
+ <bool name="show_brazil_settings">false</bool>
+</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
new file mode 100644
index 0000000..01e1835
--- /dev/null
+++ b/res/values/strings.xml
@@ -0,0 +1,141 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <!-- The name of the Cell Broadcast Receiver app. [CHAR LIMIT=NONE] -->
+ <string name="app_label">Cell Broadcasts</string>
+
+ <!-- Label for settings screen. [CHAR LIMIT=NONE] -->
+ <string name="sms_cb_settings">Cell Broadcast settings</string>
+
+ <!-- Text for dismiss button in broadcast message view dialog. [CHAR LIMIT=25] -->
+ <string name="button_dismiss">OK</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>
+ <!-- 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] -->
+ <string name="menu_delete">Delete broadcast</string>
+
+ <!-- Confirm Delete -->
+ <!-- Delete confirmation dialog title. [CHAR LIMIT=30] -->
+ <string name="confirm_dialog_title">Delete</string>
+ <!-- Delete broadcast confirmation dialog message. [CHAR LIMIT=NONE] -->
+ <string name="confirm_delete_broadcast">The broadcast message will be deleted.</string>
+ <!-- Delete all broadcasts confirmation dialog message. [CHAR LIMIT=NONE] -->
+ <string name="confirm_delete_all_broadcasts">All received broadcast messages will be deleted.</string>
+ <!-- Delete button text for delete broadcast dialog. [CHAR LIMIT=25] -->
+ <string name="button_delete">Delete</string>
+ <!-- Cancel button text for delete broadcast dialog. [CHAR LIMIT=25] -->
+ <string name="button_cancel">Cancel</string>
+
+ <!-- ETWS dialog title for Earthquake Warning. [CHAR LIMIT=50] -->
+ <string name="etws_earthquake_warning">Earthquake Warning</string>
+ <!-- ETWS dialog title for Tsunami Warning. [CHAR LIMIT=50] -->
+ <string name="etws_tsunami_warning">Tsunami Warning</string>
+ <!-- ETWS dialog title for Earthquake and Tsunami Warning. [CHAR LIMIT=50] -->
+ <string name="etws_earthquake_and_tsunami_warning">Earthquake and Tsunami Warning</string>
+ <!-- ETWS dialog title for test message. [CHAR LIMIT=50] -->
+ <string name="etws_test_message">ETWS Test Message</string>
+ <!-- ETWS dialog title for other emergency type. [CHAR LIMIT=50] -->
+ <string name="etws_other_emergency_type">Emergency Warning</string>
+ <!-- CMAS dialog title for presidential level alert. [CHAR LIMIT=50] -->
+ <string name="cmas_presidential_level_alert">Presidential Alert</string>
+ <!-- CMAS dialog title for extreme alert. [CHAR LIMIT=50] -->
+ <string name="cmas_extreme_alert">Emergency Alert: Extreme</string>
+ <!-- CMAS dialog title for severe alert. [CHAR LIMIT=50] -->
+ <string name="cmas_severe_alert">Emergency Alert: Severe</string>
+ <!-- CMAS dialog title for child abduction emergency (Amber Alert). [CHAR LIMIT=50] -->
+ <string name="cmas_amber_alert">Child Abduction (Amber Alert)</string>
+ <!-- CMAS dialog title for required monthly test. [CHAR LIMIT=50] -->
+ <string name="cmas_required_monthly_test">Emergency Alert Monthly Test</string>
+ <!-- CMAS dialog title for CMAS Exercise. [CHAR LIMIT=50] -->
+ <string name="cmas_exercise_alert">Emergency Alert (Exercise)</string>
+ <!-- CMAS dialog title for operator defined use. [CHAR LIMIT=50] -->
+ <string name="cmas_operator_defined_alert">Emergency Alert (Operator)</string>
+ <!-- Dialog title for all other message identifiers in the PWS range. [CHAR LIMIT=50] -->
+ <string name="pws_other_message_identifiers">Emergency Alert</string>
+ <!-- Dialog title for all non-emergency cell broadcasts. [CHAR LIMIT=50] -->
+ <string name="cb_other_message_identifiers">Cell Broadcast</string>
+
+ <!-- Preference category title for emergency alert settings. [CHAR LIMIT=50] -->
+ <string name="emergency_alert_settings_title">Emergency Alert Settings</string>
+ <!-- Preference title for enable emergency alerts checkbox. [CHAR LIMIT=30] -->
+ <string name="enable_emergency_alerts_title">Enable notifications</string>
+ <!-- Preference summary for enable notifications checkbox. [CHAR LIMIT=50] -->
+ <string name="enable_emergency_alerts_summary">Display emergency alert broadcasts</string>
+ <!-- Preference title for alert sound duration list. [CHAR LIMIT=30] -->
+ <string name="alert_sound_duration_title">Alert sound duration</string>
+ <!-- Do not translate. Empty summary for alert duration (set by CellBroadcastSettings). -->
+ <string name="alert_sound_duration_summary"></string>
+ <!-- Preference title for enable text-to-speech checkbox. [CHAR LIMIT=30] -->
+ <string name="enable_alert_speech_title">Speak alert message</string>
+ <!-- Preference summary for enable text-to-speech checkbox. [CHAR LIMIT=100] -->
+ <string name="enable_alert_speech_summary">Use text-to-speech to speak emergency alert messages</string>
+
+ <!-- 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>
+ <!-- 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 amber alerts checkbox. [CHAR LIMIT=50] -->
+ <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>
+ <!-- Preference title for enable CMAS test alerts checkbox. [CHAR LIMIT=30] -->
+ <string name="enable_cmas_test_alerts_title">Show 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>
+
+ <!-- Preference category title for Brazil settings. [CHAR LIMIT=50] -->
+ <string name="category_brazil_settings_title">Settings for Brazil</string>
+ <!-- Preference title for enable channel 50 alerts (Brazil only). [CHAR LIMIT=30] -->
+ <string name="enable_channel_50_alerts_title">Show channel 50 broadcasts</string>
+ <!-- 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>
+
+ <!-- Entries listed in the ListPreference for allowed alert durations. [CHAR LIMIT=30] -->
+ <string-array name="alert_sound_duration_entries">
+ <item>2 seconds</item>
+ <item>4 seconds</item>
+ <item>6 seconds</item>
+ <item>8 seconds</item>
+ <item>10 seconds</item>
+ </string-array>
+
+ <!-- Do not translate. Values that are retrieved from the ListPreference.
+ These must match the alert_sound_duration_entries above. -->
+ <string-array name="alert_sound_duration_values">
+ <item>2</item>
+ <item>4</item>
+ <item>6</item>
+ <item>8</item>
+ <item>10</item>
+ </string-array>
+</resources>
diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml
new file mode 100644
index 0000000..e1a5bc9
--- /dev/null
+++ b/res/xml/preferences.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * 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.
+ */
+-->
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <PreferenceCategory android:title="@string/emergency_alert_settings_title"
+ android:key="emergency_alert_settings">
+
+ <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>
+ <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">
+
+ <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">
+
+ <!-- Default value is true for Brazil. This preference is ignored and hidden
+ unless the boolean "show_brazil_settings" is set to true in config.xml. -->
+ <CheckBoxPreference android:defaultValue="true"
+ android:key="enable_channel_50_alerts"
+ android:summary="@string/enable_channel_50_alerts_summary"
+ android:title="@string/enable_channel_50_alerts_title" />
+
+ </PreferenceCategory>
+
+</PreferenceScreen>
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertAudio.java b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertAudio.java
new file mode 100644
index 0000000..1ae70c1
--- /dev/null
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertAudio.java
@@ -0,0 +1,341 @@
+/*
+ * 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.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.Resources;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.media.MediaPlayer.OnErrorListener;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Vibrator;
+import android.speech.tts.TextToSpeech;
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import java.util.Locale;
+
+import static com.android.cellbroadcastreceiver.CellBroadcastReceiver.DBG;
+
+/**
+ * Manages alert audio and vibration and text-to-speech. Runs as a service so that
+ * it can continue to play if another activity overrides the CellBroadcastListActivity.
+ */
+public class CellBroadcastAlertAudio extends Service implements TextToSpeech.OnInitListener,
+ TextToSpeech.OnUtteranceCompletedListener {
+ private static final String TAG = "CellBroadcastAlertAudio";
+
+ /** Action to start playing alert audio/vibration/speech. */
+ static final String ACTION_START_ALERT_AUDIO = "ACTION_START_ALERT_AUDIO";
+
+ /** Extra for alert audio duration (from settings). */
+ public static final String ALERT_AUDIO_DURATION_EXTRA =
+ "com.android.cellbroadcastreceiver.ALERT_AUDIO_DURATION";
+
+ /** Extra for message body to speak (if speech enabled in settings). */
+ public static final String ALERT_AUDIO_MESSAGE_BODY =
+ "com.android.cellbroadcastreceiver.ALERT_AUDIO_MESSAGE_BODY";
+
+ /** Extra for text-to-speech language (if speech enabled in settings). */
+ public static final String ALERT_AUDIO_MESSAGE_LANGUAGE =
+ "com.android.cellbroadcastreceiver.ALERT_AUDIO_MESSAGE_LANGUAGE";
+
+ /** Pause duration between alert sound and alert speech. */
+ private static final int PAUSE_DURATION_BEFORE_SPEAKING_MSEC = 1000;
+
+ /** 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 };
+
+ private static final int STATE_IDLE = 0;
+ private static final int STATE_ALERTING = 1;
+ private static final int STATE_PAUSING = 2;
+ private static final int STATE_SPEAKING = 3;
+
+ private int mState;
+
+ private TextToSpeech mTts;
+ private boolean mTtsEngineReady;
+
+ private String mMessageBody;
+ private String mMessageLanguage;
+ private boolean mTtsLanguageSupported;
+
+ private Vibrator mVibrator;
+ private MediaPlayer mMediaPlayer;
+ private AudioManager mAudioManager;
+ private TelephonyManager mTelephonyManager;
+ private int mInitialCallState;
+
+ // Internal messages
+ private static final int ALERT_SOUND_FINISHED = 1000;
+ private static final int ALERT_PAUSE_FINISHED = 1001;
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case ALERT_SOUND_FINISHED:
+ if (DBG) Log.v(TAG, "ALERT_SOUND_FINISHED");
+ stop(); // stop alert sound
+ // if we can speak the message text
+ if (mMessageBody != null && mTtsEngineReady && mTtsLanguageSupported) {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(ALERT_PAUSE_FINISHED),
+ PAUSE_DURATION_BEFORE_SPEAKING_MSEC);
+ mState = STATE_PAUSING;
+ } else {
+ stopSelf();
+ mState = STATE_IDLE;
+ }
+ break;
+
+ case ALERT_PAUSE_FINISHED:
+ if (DBG) Log.v(TAG, "ALERT_PAUSE_FINISHED");
+ if (mMessageBody != null && mTtsEngineReady && mTtsLanguageSupported) {
+ if (DBG) Log.v(TAG, "Speaking broadcast text: " + mMessageBody);
+ mTts.speak(mMessageBody, TextToSpeech.QUEUE_FLUSH, null);
+ mState = STATE_SPEAKING;
+ } else {
+ Log.w(TAG, "TTS engine not ready or language not supported");
+ stopSelf();
+ mState = STATE_IDLE;
+ }
+ break;
+
+ default:
+ Log.e(TAG, "Handler received unknown message, what=" + msg.what);
+ }
+ }
+ };
+
+ private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+ @Override
+ public void onCallStateChanged(int state, String ignored) {
+ // Stop the alert sound and speech if the call state changes.
+ if (state != TelephonyManager.CALL_STATE_IDLE
+ && state != mInitialCallState) {
+ stopSelf();
+ }
+ }
+ };
+
+ /**
+ * Callback from TTS engine after initialization.
+ * @param status {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
+ */
+ public void onInit(int status) {
+ if (DBG) Log.v(TAG, "onInit() TTS engine status: " + status);
+ if (status == TextToSpeech.SUCCESS) {
+ mTtsEngineReady = true;
+ // try to set the TTS language to match the broadcast
+ setTtsLanguage();
+ } else {
+ mTtsEngineReady = false;
+ mTts = null;
+ Log.e(TAG, "onInit() TTS engine error: " + status);
+ }
+ }
+
+ /**
+ * Try to set the TTS engine language to the value of mMessageLanguage.
+ * mTtsLanguageSupported will be updated based on the response.
+ */
+ private void setTtsLanguage() {
+ if (mMessageLanguage != null) {
+ if (DBG) Log.v(TAG, "Setting TTS language to '" + mMessageLanguage + '\'');
+ int result = mTts.setLanguage(new Locale(mMessageLanguage));
+ // success values are >= 0, failure returns negative value
+ if (DBG) Log.v(TAG, "TTS setLanguage() returned: " + result);
+ mTtsLanguageSupported = result >= 0;
+ } else {
+ // try to use the default TTS language for broadcasts with no language specified
+ if (DBG) Log.v(TAG, "No language specified in broadcast: using default");
+ mTtsLanguageSupported = true;
+ }
+ }
+
+ /**
+ * Callback from TTS engine.
+ * @param utteranceId the identifier of the utterance.
+ */
+ public void onUtteranceCompleted(String utteranceId) {
+ stopSelf();
+ }
+
+ @Override
+ public void onCreate() {
+ mVibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
+ mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ // Listen for incoming calls to kill the alarm.
+ mTelephonyManager =
+ (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
+ mTelephonyManager.listen(
+ mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
+ CellBroadcastAlertWakeLock.acquireCpuWakeLock(this);
+ }
+
+ @Override
+ public void onDestroy() {
+ stop();
+ // Stop listening for incoming calls.
+ mTelephonyManager.listen(mPhoneStateListener, 0);
+ CellBroadcastAlertWakeLock.releaseCpuLock();
+ // shutdown TTS engine
+ if (mTts != null) {
+ mTts.stop();
+ mTts.shutdown();
+ }
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ // No intent, tell the system not to restart us.
+ if (intent == null) {
+ stopSelf();
+ return START_NOT_STICKY;
+ }
+
+ // This extra should always be provided by CellBroadcastAlertService,
+ // but default to 4 seconds just to be safe
+ int duration = intent.getIntExtra(ALERT_AUDIO_DURATION_EXTRA, 4);
+
+ // Get text to speak (if enabled by user)
+ mMessageBody = intent.getStringExtra(ALERT_AUDIO_MESSAGE_BODY);
+ mMessageLanguage = intent.getStringExtra(ALERT_AUDIO_MESSAGE_LANGUAGE);
+
+ if (mMessageBody != null) {
+ if (mTts == null) {
+ mTts = new TextToSpeech(this, this);
+ } else if (mTtsEngineReady) {
+ setTtsLanguage();
+ }
+ }
+
+ play(duration * 1000); // convert to milliseconds
+
+ // Record the initial call state here so that the new alarm has the
+ // newest state.
+ mInitialCallState = mTelephonyManager.getCallState();
+
+ return START_STICKY;
+ }
+
+ // Volume suggested by media team for in-call alarms.
+ private static final float IN_CALL_VOLUME = 0.125f;
+
+ /**
+ * Start playing the alert sound, and send delayed message when it's time to stop.
+ * @param duration the alert sound duration in milliseconds
+ */
+ private void play(int duration) {
+ // stop() checks to see if we are already playing.
+ stop();
+
+ if (DBG) Log.v(TAG, "play()");
+
+ // future optimization: reuse media player object
+ mMediaPlayer = new MediaPlayer();
+ mMediaPlayer.setOnErrorListener(new OnErrorListener() {
+ public boolean onError(MediaPlayer mp, int what, int extra) {
+ Log.e(TAG, "Error occurred while playing audio.");
+ mp.stop();
+ mp.release();
+ mMediaPlayer = null;
+ return true;
+ }
+ });
+
+ try {
+ // Check if we are in a call. If we are, play the alert
+ // sound at a low volume to not disrupt the call.
+ if (mTelephonyManager.getCallState()
+ != TelephonyManager.CALL_STATE_IDLE) {
+ Log.v(TAG, "in call: reducing volume");
+ mMediaPlayer.setVolume(IN_CALL_VOLUME, IN_CALL_VOLUME);
+ }
+ setDataSourceFromResource(getResources(), mMediaPlayer,
+ R.raw.attention_signal);
+ mAudioManager.requestAudioFocus(null, AudioManager.STREAM_ALARM,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+ startAlarm(mMediaPlayer);
+ } catch (Exception ex) {
+ Log.e(TAG, "Failed to play alert sound", ex);
+ }
+
+ /* Start the vibrator after everything is ok with the media player */
+ mVibrator.vibrate(sVibratePattern, 1);
+
+ // stop alert after the specified duration
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(ALERT_SOUND_FINISHED), duration);
+ mState = STATE_ALERTING;
+ }
+
+ // Do the common stuff when starting the alarm.
+ private static void startAlarm(MediaPlayer player)
+ throws java.io.IOException, IllegalArgumentException,
+ IllegalStateException {
+ player.setAudioStreamType(AudioManager.STREAM_ALARM);
+ player.setLooping(true);
+ player.prepare();
+ player.start();
+ }
+
+ private static void setDataSourceFromResource(Resources resources,
+ MediaPlayer player, int res) throws java.io.IOException {
+ AssetFileDescriptor afd = resources.openRawResourceFd(res);
+ if (afd != null) {
+ player.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(),
+ afd.getLength());
+ afd.close();
+ }
+ }
+
+ /**
+ * Stops alert audio and speech.
+ */
+ public void stop() {
+ if (DBG) Log.v(TAG, "stop()");
+
+ mHandler.removeMessages(ALERT_SOUND_FINISHED);
+ mHandler.removeMessages(ALERT_PAUSE_FINISHED);
+
+ if (mState == STATE_ALERTING) {
+ // Stop audio playing
+ if (mMediaPlayer != null) {
+ mMediaPlayer.stop();
+ mMediaPlayer.release();
+ mMediaPlayer = null;
+ }
+
+ // Stop vibrator
+ mVibrator.cancel();
+ } else if (mState == STATE_SPEAKING && mTts != null) {
+ mTts.stop();
+ }
+ mAudioManager.abandonAudioFocus(null);
+ mState = STATE_IDLE;
+ }
+}
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertDialog.java b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertDialog.java
new file mode 100644
index 0000000..eed3d20
--- /dev/null
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertDialog.java
@@ -0,0 +1,183 @@
+/*
+ * 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.app.AlertDialog;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.view.WindowManager;
+import android.widget.ImageView;
+
+/**
+ * Custom alert dialog with optional flashing warning icon.
+ * Alert audio and text-to-speech handled by {@link CellBroadcastAlertAudio}.
+ */
+public class CellBroadcastAlertDialog extends AlertDialog {
+
+ /** Whether to show the flashing warning icon. */
+ private final boolean mShowWarningIcon;
+
+ /** 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() {
+ @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);
+ }
+ }
+
+ @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);
+ }
+ }
+
+ /**
+ * 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);
+ }
+ }
+
+ /**
+ * 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;
+ }
+ }
+
+ /**
+ * Ignore the back button for emergency alerts (user must dismiss with button).
+ */
+ @Override
+ public void onBackPressed() {
+ if (!mShowWarningIcon) {
+ super.onBackPressed();
+ }
+ }
+}
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertService.java b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertService.java
new file mode 100644
index 0000000..bb4745e
--- /dev/null
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertService.java
@@ -0,0 +1,231 @@
+/*
+ * 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.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.preference.PreferenceManager;
+import android.provider.Telephony;
+import android.telephony.SmsCbConstants;
+import android.telephony.SmsCbMessage;
+import android.util.Log;
+
+/**
+ * This service manages the display and animation of broadcast messages.
+ * Emergency messages display with a flashing animated exclamation mark icon,
+ * and an alert tone is played when the alert is first shown to the user
+ * (but not when the user views a previously received broadcast).
+ */
+public class CellBroadcastAlertService extends Service {
+ private static final String TAG = "CellBroadcastAlertService";
+
+ /** Identifier for notification ID extra. */
+ public static final String SMS_CB_NOTIFICATION_ID_EXTRA =
+ "com.android.cellbroadcastreceiver.SMS_CB_NOTIFICATION_ID";
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ String action = intent.getAction();
+ if (Telephony.Sms.Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION.equals(action) ||
+ Telephony.Sms.Intents.SMS_CB_RECEIVED_ACTION.equals(action)) {
+ handleCellBroadcastIntent(intent);
+ } else {
+ Log.e(TAG, "Unrecognized intent action: " + action);
+ }
+ stopSelf(); // this service always stops after processing the intent
+ return START_NOT_STICKY;
+ }
+
+ private void handleCellBroadcastIntent(Intent intent) {
+ Bundle extras = intent.getExtras();
+ if (extras == null) {
+ Log.e(TAG, "received SMS_CB_RECEIVED_ACTION with no extras!");
+ 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;
+ }
+
+ // 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]);
+ 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() +
+ " by user preference");
+ return;
+ }
+
+ // add notification to the bar
+ addToNotificationBar(cbm);
+ if (cbm.isEmergencyAlertMessage()) {
+ // 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);
+ }
+ // write to database on a separate service thread
+ Intent dbWriteIntent = new Intent(this, CellBroadcastDatabaseService.class);
+ dbWriteIntent.setAction(CellBroadcastDatabaseService.ACTION_INSERT_NEW_BROADCAST);
+ dbWriteIntent.putExtra(CellBroadcastMessage.SMS_CB_MESSAGE_EXTRA, cbm);
+ startService(dbWriteIntent);
+ }
+
+ /**
+ * Filter out broadcasts on the test channels that the user has not enabled,
+ * and types of notifications that the user is not interested in receiving.
+ * This allows us to enable an entire range of message identifiers in the
+ * radio and not have to explicitly disable the message identifiers for
+ * test broadcasts. In the unlikely event that the default shared preference
+ * values were not initialized in CellBroadcastReceiverApp, the second parameter
+ * to the getBoolean() calls match the default values in res/xml/preferences.xml.
+ *
+ * @param message the message to check
+ * @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;
+ }
+ }
+
+ private void addToNotificationBar(CellBroadcastMessage message) {
+ int channelTitleId = message.getDialogTitleResource();
+ CharSequence channelName = getText(channelTitleId);
+ String messageBody = message.getMessageBody();
+
+ Notification notification = new Notification(R.drawable.stat_color_warning,
+ channelName, System.currentTimeMillis());
+
+ int notificationId = CellBroadcastReceiverApp.getCellBroadcastReceiverApp()
+ .getNextNotificationId();
+
+ PendingIntent pi = PendingIntent.getActivity(this, 0, createDisplayMessageIntent(
+ this, message, notificationId), 0);
+
+ notification.setLatestEventInfo(this, channelName, messageBody, pi);
+
+ if (message.isEmergencyAlertMessage()) {
+ // Emergency: open notification immediately
+ notification.fullScreenIntent = pi;
+ // use default notification lights (CellBroadcastAlertAudio plays sound/vibration)
+ notification.defaults = Notification.DEFAULT_LIGHTS;
+ } else {
+ // use default sound/vibration/lights for non-emergency broadcasts
+ notification.defaults = Notification.DEFAULT_ALL;
+ }
+
+ Log.i(TAG, "addToNotificationBar notificationId: " + notificationId);
+
+ NotificationManager notificationManager =
+ (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
+
+ notificationManager.notify(notificationId, notification);
+ }
+
+ static Intent createDisplayMessageIntent(Context context,
+ 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.putExtra(CellBroadcastMessage.SMS_CB_MESSAGE_EXTRA, message);
+ intent.putExtra(SMS_CB_NOTIFICATION_ID_EXTRA, notificationId);
+
+ // 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
+ // intent of a previous message and notification.
+ intent.setType(Integer.toString(notificationId));
+
+ return intent;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null; // clients can't bind to this service
+ }
+}
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertWakeLock.java b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertWakeLock.java
new file mode 100644
index 0000000..2d23031
--- /dev/null
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertWakeLock.java
@@ -0,0 +1,52 @@
+/*
+ * 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
new file mode 100644
index 0000000..d4c7c53
--- /dev/null
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastConfigService.java
@@ -0,0 +1,95 @@
+/*
+ * 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.app.IntentService;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.preference.PreferenceManager;
+import android.telephony.SmsCbConstants;
+import android.telephony.SmsManager;
+import android.util.Log;
+
+import com.android.internal.telephony.gsm.SmsCbHeader;
+
+import static com.android.cellbroadcastreceiver.CellBroadcastReceiver.DBG;
+
+/**
+ * This service manages enabling and disabling ranges of message identifiers
+ * that the radio should listen for. It operates independently of the other
+ * services and runs at boot time and after exiting airplane mode.
+ *
+ * Note that the entire range of emergency channels is enabled. Test messages
+ * and lower priority broadcasts are filtered out in CellBroadcastAlertService
+ * if the user has not enabled them in settings.
+ *
+ * TODO: add notification to re-enable channels after a radio reset.
+ */
+public class CellBroadcastConfigService extends IntentService {
+ private static final String TAG = "CellBroadcastConfigService";
+
+ static final String ACTION_ENABLE_CHANNELS = "ACTION_ENABLE_CHANNELS";
+
+ public CellBroadcastConfigService() {
+ super(TAG); // use class name for worker thread name
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ if (ACTION_ENABLE_CHANNELS.equals(intent.getAction())) {
+ try {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ Resources res = getResources();
+
+ boolean enableEmergencyAlerts = prefs.getBoolean(
+ CellBroadcastSettings.KEY_ENABLE_EMERGENCY_ALERTS, true);
+
+ boolean enableChannel50Alerts = res.getBoolean(R.bool.show_brazil_settings) &&
+ prefs.getBoolean(CellBroadcastSettings.KEY_ENABLE_CHANNEL_50_ALERTS, true);
+
+ SmsManager manager = SmsManager.getDefault();
+ if (enableEmergencyAlerts) {
+ if (DBG) Log.d(TAG, "enabling emergency cell broadcast channels");
+ manager.enableCellBroadcastRange(
+ SmsCbConstants.MESSAGE_ID_PWS_FIRST_IDENTIFIER,
+ SmsCbConstants.MESSAGE_ID_PWS_LAST_IDENTIFIER);
+ if (DBG) Log.d(TAG, "enabled emergency cell broadcast channels");
+ } else {
+ // we may have enabled these channels previously, so try to disable them
+ if (DBG) Log.d(TAG, "disabling emergency cell broadcast channels");
+ manager.disableCellBroadcastRange(
+ SmsCbConstants.MESSAGE_ID_PWS_FIRST_IDENTIFIER,
+ SmsCbConstants.MESSAGE_ID_PWS_LAST_IDENTIFIER);
+ if (DBG) Log.d(TAG, "disabled emergency cell broadcast channels");
+ }
+
+ if (enableChannel50Alerts) {
+ if (DBG) Log.d(TAG, "enabling cell broadcast channel 50");
+ manager.enableCellBroadcast(50);
+ if (DBG) Log.d(TAG, "enabled cell broadcast channel 50");
+ } else {
+ if (DBG) Log.d(TAG, "disabling cell broadcast channel 50");
+ manager.disableCellBroadcast(50);
+ if (DBG) Log.d(TAG, "disabled cell broadcast channel 50");
+ }
+ } catch (Exception ex) {
+ Log.e(TAG, "exception enabling cell broadcast channels", ex);
+ }
+ }
+ }
+}
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastDatabase.java b/src/com/android/cellbroadcastreceiver/CellBroadcastDatabase.java
new file mode 100644
index 0000000..a27be4b
--- /dev/null
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastDatabase.java
@@ -0,0 +1,150 @@
+/*
+ * 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.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.provider.BaseColumns;
+
+public class CellBroadcastDatabase {
+ private 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;
+
+ static final class Columns implements BaseColumns {
+
+ private Columns() {}
+
+ /**
+ * Message geographical scope.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String GEOGRAPHICAL_SCOPE = "geo_scope";
+
+ /**
+ * Message serial number.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String SERIAL_NUMBER = "serial_number";
+
+ /**
+ * Message code.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MESSAGE_CODE = "message_code";
+
+ /**
+ * Message identifier.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MESSAGE_IDENTIFIER = "message_id";
+
+ /**
+ * Message language code.
+ * <P>Type: TEXT</P>
+ */
+ public static final String LANGUAGE_CODE = "language";
+
+ /**
+ * Message body.
+ * <P>Type: TEXT</P>
+ */
+ public static final String MESSAGE_BODY = "body";
+
+ /**
+ * Message delivery time.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String DELIVERY_TIME = "date";
+
+ /**
+ * Has the message been viewed?
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String MESSAGE_READ = "read";
+
+ /**
+ * Query for list view adapter.
+ */
+ static final String[] QUERY_COLUMNS = {
+ _ID,
+ GEOGRAPHICAL_SCOPE,
+ SERIAL_NUMBER,
+ MESSAGE_CODE,
+ MESSAGE_IDENTIFIER,
+ LANGUAGE_CODE,
+ MESSAGE_BODY,
+ DELIVERY_TIME,
+ MESSAGE_READ,
+ };
+ }
+
+ /* Column indexes for reading from cursor. */
+
+ 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 class DatabaseHelper extends SQLiteOpenHelper {
+
+ DatabaseHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE " + TABLE_NAME + " ("
+ + Columns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ + Columns.GEOGRAPHICAL_SCOPE + " INTEGER,"
+ + Columns.SERIAL_NUMBER + " INTEGER,"
+ + Columns.MESSAGE_CODE + " INTEGER,"
+ + Columns.MESSAGE_IDENTIFIER + " INTEGER,"
+ + Columns.LANGUAGE_CODE + " TEXT,"
+ + Columns.MESSAGE_BODY + " TEXT,"
+ + Columns.DELIVERY_TIME + " INTEGER,"
+ + Columns.MESSAGE_READ + " INTEGER);");
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ // ignored for now
+ }
+ }
+
+ /**
+ * 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);
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastDatabaseService.java b/src/com/android/cellbroadcastreceiver/CellBroadcastDatabaseService.java
new file mode 100644
index 0000000..d27e21e
--- /dev/null
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastDatabaseService.java
@@ -0,0 +1,151 @@
+/*
+ * 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.app.IntentService;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.database.sqlite.SQLiteDatabase;
+import android.util.Log;
+
+/**
+ * Service to update the SQLite database to add a new broadcast message,
+ * or to delete one or all previously received broadcasts.
+ */
+public class CellBroadcastDatabaseService extends IntentService {
+ private static final String TAG = "CellBroadcastDatabaseService";
+
+ /** Action to insert a new message (passed as CellBroadcastMessage extra). */
+ static final String ACTION_INSERT_NEW_BROADCAST = "ACTION_INSERT_NEW_BROADCAST";
+
+ /** Action to delete a single broadcast (row ID passed as extra). */
+ static final String ACTION_DELETE_BROADCAST = "ACTION_DELETE_BROADCAST";
+
+ /** Action to mark a broadcast as read by the user (by row ID or delivery time extra). */
+ static final String ACTION_MARK_BROADCAST_READ = "ACTION_MARK_BROADCAST_READ";
+
+ /** Action to delete all broadcasts from database (no extras). */
+ static final String ACTION_DELETE_ALL_BROADCASTS = "ACTION_DELETE_ALL_BROADCASTS";
+
+ /** Identifier for getExtra() for row ID to delete or mark read. */
+ public static final String DATABASE_ROW_ID_EXTRA =
+ "com.android.cellbroadcastreceiver.DATABASE_ROW_ID";
+
+ /** Identifier for getExtra() for delivery time of broadcast to mark read. */
+ public static final String DATABASE_DELIVERY_TIME_EXTRA =
+ "com.android.cellbroadcastreceiver.DATABASE_DELIVERY_TIME";
+
+ private SQLiteDatabase mBroadcastDb;
+
+ /** Callback for the active list activity when the contents change. */
+ private static CellBroadcastListActivity sActiveListActivity;
+
+ 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) {
+ mBroadcastDb.close();
+ mBroadcastDb = null;
+ }
+ }
+
+ static void setActiveListActivity(CellBroadcastListActivity activity) {
+ sActiveListActivity = activity;
+ }
+
+ @Override
+ public void onHandleIntent(Intent intent) {
+ // TODO: security check to detect malicious broadcast injections
+ String action = intent.getAction();
+ boolean notifyActiveListActivity = false;
+ if (ACTION_INSERT_NEW_BROADCAST.equals(action)) {
+ CellBroadcastMessage cbm = intent.getParcelableExtra(
+ CellBroadcastMessage.SMS_CB_MESSAGE_EXTRA);
+ if (cbm == null) {
+ Log.e(TAG, "ACTION_INSERT_NEW_BROADCAST with no CB message extra");
+ return;
+ }
+
+ ContentValues cv = cbm.getContentValues();
+ long rowId = mBroadcastDb.insert(CellBroadcastDatabase.TABLE_NAME, null, cv);
+ if (rowId == -1) {
+ Log.e(TAG, "failed to insert new broadcast into database!");
+ } else {
+ notifyActiveListActivity = true;
+ }
+ } else if (ACTION_DELETE_BROADCAST.equals(action)) {
+ long rowId = intent.getLongExtra(DATABASE_ROW_ID_EXTRA, -1);
+ if (rowId == -1) {
+ Log.e(TAG, "ACTION_DELETE_BROADCAST missing row ID to delete");
+ return;
+ }
+
+ int rowCount = mBroadcastDb.delete(CellBroadcastDatabase.TABLE_NAME,
+ CellBroadcastDatabase.Columns._ID + "=?",
+ new String[]{Long.toString(rowId)});
+ if (rowCount != 0) {
+ notifyActiveListActivity = true;
+ }
+ } else if (ACTION_DELETE_ALL_BROADCASTS.equals(action)) {
+ mBroadcastDb.delete(CellBroadcastDatabase.TABLE_NAME, null, null);
+ notifyActiveListActivity = true;
+ } else if (ACTION_MARK_BROADCAST_READ.equals(action)) {
+ long rowId = intent.getLongExtra(DATABASE_ROW_ID_EXTRA, -1);
+ long deliveryTime = intent.getLongExtra(DATABASE_DELIVERY_TIME_EXTRA, -1);
+ if (rowId == -1 && deliveryTime == -1) {
+ Log.e(TAG, "ACTION_MARK_BROADCAST_READ missing row ID or delivery time");
+ return;
+ }
+ ContentValues cv = new ContentValues(1);
+ cv.put(CellBroadcastDatabase.Columns.MESSAGE_READ, 1);
+ int rowCount;
+ if (rowId != -1) {
+ rowCount = mBroadcastDb.update(CellBroadcastDatabase.TABLE_NAME, cv,
+ CellBroadcastDatabase.Columns._ID + "=?",
+ new String[]{Long.toString(rowId)});
+ } else {
+ rowCount = mBroadcastDb.update(CellBroadcastDatabase.TABLE_NAME, cv,
+ CellBroadcastDatabase.Columns.DELIVERY_TIME + "=?",
+ new String[]{Long.toString(deliveryTime)});
+ }
+ if (rowCount != 0) {
+ notifyActiveListActivity = true;
+ }
+ } else {
+ Log.e(TAG, "ignoring unexpected Intent with action " + action);
+ }
+ if (notifyActiveListActivity && sActiveListActivity != null) {
+ sActiveListActivity.databaseContentChanged();
+ }
+ }
+}
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastListActivity.java b/src/com/android/cellbroadcastreceiver/CellBroadcastListActivity.java
new file mode 100644
index 0000000..2d25b99
--- /dev/null
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastListActivity.java
@@ -0,0 +1,278 @@
+/*
+ * 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.app.AlertDialog;
+import android.app.ListActivity;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.os.Bundle;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnCreateContextMenuListener;
+import android.widget.ListView;
+import android.widget.TextView;
+
+/**
+ * This activity provides a list view of received cell broadcasts.
+ */
+public class CellBroadcastListActivity extends ListActivity {
+ private 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;
+
+ // 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;
+
+ private CellBroadcastListAdapter mListAdapter;
+
+ private SQLiteDatabase mBroadcastDb;
+
+ private Cursor mAdapterCursor;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.cell_broadcast_list_screen);
+
+ ListView listView = getListView();
+ listView.setOnCreateContextMenuListener(mOnCreateContextMenuListener);
+
+ if (mBroadcastDb == null) {
+ CellBroadcastDatabase.DatabaseHelper helper =
+ new CellBroadcastDatabase.DatabaseHelper(this);
+ mBroadcastDb = helper.getReadableDatabase();
+ }
+
+ if (mAdapterCursor == null) {
+ mAdapterCursor = CellBroadcastDatabase.getCursor(mBroadcastDb);
+ }
+
+ mListAdapter = new CellBroadcastListAdapter(this, mAdapterCursor);
+ setListAdapter(mListAdapter);
+
+ CellBroadcastDatabaseService.setActiveListActivity(this);
+
+ parseIntent(getIntent());
+ }
+
+ @Override
+ protected void onDestroy() {
+ if (mAdapterCursor != null) {
+ mAdapterCursor.close();
+ mAdapterCursor = null;
+ }
+ if (mBroadcastDb != null) {
+ mBroadcastDb.close();
+ mBroadcastDb = null;
+ }
+ CellBroadcastDatabaseService.setActiveListActivity(null);
+ super.onDestroy();
+ }
+
+ /** Callback from CellBroadcastDatabaseService after content changes. */
+ void databaseContentChanged() {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ mAdapterCursor.requery();
+ }
+ });
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ // TODO: how do multiple messages stack together?
+ // removeDialog(DIALOG_SHOW_MESSAGE);
+ parseIntent(intent);
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ menu.clear();
+
+ if (mListAdapter.getCount() > 0) {
+ 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);
+
+ return true;
+ }
+
+ @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;
+ }
+ 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 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);
+ }
+ };
+
+ @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));
+ break;
+
+ case MENU_VIEW:
+ showDialogAndMarkRead(cursor);
+ break;
+
+ default:
+ break;
+ }
+ }
+ return super.onContextItemSelected(item);
+ }
+
+ private void showDialogAndMarkRead(Cursor cursor) {
+ CellBroadcastMessage cbm = CellBroadcastMessage.createFromCursor(cursor);
+ // show emergency alerts with the warning icon, but don't play alert tone
+ CellBroadcastAlertDialog dialog = new CellBroadcastAlertDialog(this,
+ cbm.getDialogTitleResource(), cbm.getMessageBody(),
+ cbm.isPublicAlertMessage(), 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);
+ }
+
+ /**
+ * 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 DeleteThreadListener(long rowId) {
+ mRowId = rowId;
+ }
+
+ public void onClick(DialogInterface dialog, int whichButton) {
+ if (mRowId != -1) {
+ // delete from database on a separate service thread
+ Intent dbWriteIntent = new Intent(CellBroadcastListActivity.this,
+ 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);
+ }
+ dialog.dismiss();
+ }
+ }
+
+ private void parseIntent(Intent intent) {
+ if (intent == null) {
+ return;
+ }
+ Bundle extras = intent.getExtras();
+ if (extras == null) {
+ return;
+ }
+
+ CellBroadcastMessage cbm = extras.getParcelable(CellBroadcastMessage.SMS_CB_MESSAGE_EXTRA);
+ int notificationId = extras.getInt(CellBroadcastAlertService.SMS_CB_NOTIFICATION_ID_EXTRA);
+
+ // Dismiss the notification that brought us here.
+ NotificationManager notificationManager =
+ (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.cancel(notificationId);
+
+ boolean isEmergencyAlert = cbm.isPublicAlertMessage();
+
+ CellBroadcastAlertDialog dialog = new CellBroadcastAlertDialog(this,
+ cbm.getDialogTitleResource(), cbm.getMessageBody(),
+ isEmergencyAlert, cbm.getDeliveryTime());
+ dialog.show();
+ }
+}
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastListAdapter.java b/src/com/android/cellbroadcastreceiver/CellBroadcastListAdapter.java
new file mode 100644
index 0000000..d0b7964
--- /dev/null
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastListAdapter.java
@@ -0,0 +1,67 @@
+/*
+ * 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.database.Cursor;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CursorAdapter;
+
+/**
+ * The back-end data adapter for {@link CellBroadcastListActivity}.
+ */
+public class CellBroadcastListAdapter extends CursorAdapter {
+ private static final String TAG = "CellBroadcastListAdapter";
+
+ public CellBroadcastListAdapter(Context context, Cursor cursor) {
+ super(context, cursor, true);
+ }
+
+ /**
+ * Makes a new view to hold the data pointed to by cursor.
+ * @param context Interface to application's global information
+ * @param cursor The cursor from which to get the data. The cursor is already
+ * moved to the correct position.
+ * @param parent The parent to which the new view is attached to
+ * @return the newly created view.
+ */
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ CellBroadcastMessage message = CellBroadcastMessage.createFromCursor(cursor);
+
+ LayoutInflater factory = LayoutInflater.from(context);
+ CellBroadcastListItem listItem = (CellBroadcastListItem) factory.inflate(
+ R.layout.cell_broadcast_list_item, parent, false);
+
+ listItem.bind(message);
+ return listItem;
+ }
+
+ /**
+ * Bind an existing view to the data pointed to by cursor
+ * @param view Existing view, returned earlier by newView
+ * @param context Interface to application's global information
+ * @param cursor The cursor from which to get the data. The cursor is already
+ * moved to the correct position.
+ */
+ public void bindView(View view, Context context, Cursor cursor) {
+ CellBroadcastMessage message = CellBroadcastMessage.createFromCursor(cursor);
+ CellBroadcastListItem listItem = (CellBroadcastListItem) view;
+ listItem.bind(message);
+ }
+}
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastListItem.java b/src/com/android/cellbroadcastreceiver/CellBroadcastListItem.java
new file mode 100644
index 0000000..6f64236
--- /dev/null
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastListItem.java
@@ -0,0 +1,107 @@
+/*
+ * 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.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.
+ */
+public class CellBroadcastListItem extends RelativeLayout {
+ private static final String TAG = "CellBroadcastListItem";
+ private static final boolean DEBUG = false;
+
+ private CellBroadcastMessage mCbMessage;
+
+ private TextView mChannelView;
+ 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);
+ }
+
+ CellBroadcastMessage getMessage() {
+ return mCbMessage;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mChannelView = (TextView) findViewById(R.id.channel);
+ mDateView = (TextView) findViewById(R.id.date);
+ mMessageView = (TextView) findViewById(R.id.message);
+ }
+
+ /**
+ * Only used for header binding.
+ * @param message the message contents to bind
+ */
+ public void bind(CellBroadcastMessage message) {
+ mCbMessage = message;
+
+ Drawable background = message.isRead() ?
+ getResources().getDrawable(R.drawable.list_item_background_read) :
+ getResources().getDrawable(R.drawable.list_item_background_unread);
+
+ setBackgroundDrawable(background);
+
+ mChannelView.setText(message.getDialogTitleResource());
+ mDateView.setText(message.getDateString(getContext()));
+ mMessageView.setText(formatMessage(message));
+ }
+
+ private static CharSequence formatMessage(CellBroadcastMessage message) {
+ String body = message.getMessageBody();
+
+ SpannableStringBuilder buf = new SpannableStringBuilder(body);
+
+ // Unread messages are shown in bold
+ if (!message.isRead()) {
+ buf.setSpan(STYLE_BOLD, 0, buf.length(),
+ Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
+ }
+ return buf;
+ }
+
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ // Speak the date first, then channel name, then message body
+ event.getText().add(mCbMessage.getSpokenDateString(getContext()));
+ mChannelView.dispatchPopulateAccessibilityEvent(event);
+ mMessageView.dispatchPopulateAccessibilityEvent(event);
+ return true;
+ }
+}
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastMessage.java b/src/com/android/cellbroadcastreceiver/CellBroadcastMessage.java
new file mode 100644
index 0000000..a4d3bc2
--- /dev/null
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastMessage.java
@@ -0,0 +1,316 @@
+/*
+ * 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.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.SmsCbConstants;
+import android.telephony.SmsCbMessage;
+import android.text.format.DateUtils;
+
+import com.android.internal.telephony.gsm.SmsCbHeader;
+
+/**
+ * Application wrapper for {@link SmsCbMessage}. This is Parcelable so that
+ * decoded broadcast message objects can be passed between running Services.
+ * New broadcasts are received by {@link CellBroadcastReceiver},
+ * displayed by {@link CellBroadcastAlertService}, and saved to SQLite by
+ * {@link CellBroadcastDatabaseService}.
+ */
+public class CellBroadcastMessage implements Parcelable {
+
+ /** Identifier for getExtra() when adding this object to an Intent. */
+ 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;
+ 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();
+ 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;
+ mDeliveryTime = deliveryTime;
+ mIsRead = isRead;
+ }
+
+ /** 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);
+ out.writeLong(mDeliveryTime);
+ out.writeInt(mIsRead ? 1 : 0);
+ }
+
+ 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));
+ }
+
+ public CellBroadcastMessage[] newArray(int size) {
+ return new CellBroadcastMessage[size];
+ }
+ };
+
+ /**
+ * Create a CellBroadcastMessage from a row in the database.
+ * @param cursor an open SQLite cursor pointing to the row to read
+ * @return the new CellBroadcastMessage
+ */
+ 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);
+ String language = cursor.getString(CellBroadcastDatabase.COLUMN_LANGUAGE_CODE);
+ String body = cursor.getString(CellBroadcastDatabase.COLUMN_MESSAGE_BODY);
+ 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 a ContentValues object for insertion into the database.
+ * @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);
+ cv.put(CellBroadcastDatabase.Columns.DELIVERY_TIME, mDeliveryTime);
+ cv.put(CellBroadcastDatabase.Columns.MESSAGE_READ, mIsRead);
+ return cv;
+ }
+
+ /**
+ * Set or clear the "read message" flag.
+ * @param isRead true if the message has been read; false if not
+ */
+ public void setIsRead(boolean isRead) {
+ mIsRead = isRead;
+ }
+
+ public int getGeographicalScope() {
+ return mGeographicalScope;
+ }
+
+ public int getSerialNumber() {
+ return mSerialNumber;
+ }
+
+ public int getMessageCode() {
+ return mMessageCode;
+ }
+
+ public int getMessageIdentifier() {
+ return mMessageIdentifier;
+ }
+
+ public String getLanguageCode() {
+ return mLanguageCode;
+ }
+
+ public long getDeliveryTime() {
+ return mDeliveryTime;
+ }
+
+ public String getMessageBody() {
+ return mMessageBody;
+ }
+
+ public boolean isRead() {
+ return mIsRead;
+ }
+
+ /**
+ * Return whether the broadcast is an emergency (PWS) message type.
+ * This includes lower priority test messages and Amber alerts.
+ *
+ * All public alerts show the flashing warning icon in the dialog,
+ * but only emergency alerts play the alert sound and speak the message.
+ *
+ * @return true if the message is PWS type; false otherwise
+ */
+ public boolean isPublicAlertMessage() {
+ return SmsCbHeader.isEmergencyMessage(mMessageIdentifier);
+ }
+
+ /**
+ * Returns whether the broadcast is an emergency (PWS) message type,
+ * including test messages, but excluding lower priority Amber alert broadcasts.
+ *
+ * @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;
+ }
+
+ /**
+ * Return whether the broadcast is an ETWS emergency message type.
+ * @return true if the message is ETWS emergency type; false otherwise
+ */
+ public boolean isEtwsMessage() {
+ return SmsCbHeader.isEtwsMessage(mMessageIdentifier);
+ }
+
+ /**
+ * Return whether the broadcast is a CMAS emergency message type.
+ * @return true if the message is CMAS emergency type; false otherwise
+ */
+ public boolean isCmasMessage() {
+ return SmsCbHeader.isCmasMessage(mMessageIdentifier);
+ }
+
+ /**
+ * Return whether the broadcast is an ETWS popup alert.
+ * This method checks the message ID and the message code.
+ * @return true if the message indicates an ETWS popup alert
+ */
+ public boolean isEtwsPopupAlert() {
+ return SmsCbHeader.isEtwsMessage(mMessageIdentifier) &&
+ SmsCbHeader.isEtwsPopupAlert(mMessageCode);
+ }
+
+ /**
+ * Return whether the broadcast is an ETWS emergency user alert.
+ * This method checks the message ID and the message code.
+ * @return true if the message indicates an ETWS emergency user alert
+ */
+ public boolean isEtwsEmergencyUserAlert() {
+ return SmsCbHeader.isEtwsMessage(mMessageIdentifier) &&
+ SmsCbHeader.isEtwsEmergencyUserAlert(mMessageCode);
+ }
+
+ 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;
+
+ case SmsCbConstants.MESSAGE_ID_ETWS_OTHER_EMERGENCY_TYPE:
+ return R.string.etws_other_emergency_type;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL:
+ 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 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 SmsCbConstants.MESSAGE_ID_CMAS_ALERT_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 SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXERCISE:
+ return R.string.cmas_exercise_alert;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE:
+ return R.string.cmas_operator_defined_alert;
+
+ default:
+ if (SmsCbHeader.isEmergencyMessage(mMessageIdentifier)) {
+ return R.string.pws_other_message_identifiers;
+ } else {
+ return R.string.cb_other_message_identifiers;
+ }
+ }
+ }
+
+ /**
+ * Return the abbreviated date string for the message delivery time.
+ * @param context the context object
+ * @return a String to use in the broadcast list UI
+ */
+ String getDateString(Context context) {
+ int flags = DateUtils.FORMAT_NO_NOON_MIDNIGHT | DateUtils.FORMAT_SHOW_TIME |
+ DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE |
+ DateUtils.FORMAT_CAP_AMPM;
+ return DateUtils.formatDateTime(context, mDeliveryTime, flags);
+ }
+
+ /**
+ * Return the date string for the message delivery time, suitable for text-to-speech.
+ * @param context the context object
+ * @return a String for populating the list item AccessibilityEvent for TTS
+ */
+ String getSpokenDateString(Context context) {
+ int flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE;
+ return DateUtils.formatDateTime(context, mDeliveryTime, flags);
+ }
+}
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastReceiver.java b/src/com/android/cellbroadcastreceiver/CellBroadcastReceiver.java
new file mode 100644
index 0000000..bce3afd
--- /dev/null
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastReceiver.java
@@ -0,0 +1,97 @@
+/*
+ * 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.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.provider.Telephony;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.android.internal.telephony.ITelephony;
+
+public class CellBroadcastReceiver extends BroadcastReceiver {
+ private static final String TAG = "CellBroadcastReceiver";
+ static final boolean DBG = true; // TODO: change to false before ship
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ onReceiveWithPrivilege(context, intent, false);
+ }
+
+ protected void onReceiveWithPrivilege(Context context, Intent intent, boolean privileged) {
+ if (DBG) Log.d(TAG, "onReceive " + intent);
+
+ String action = intent.getAction();
+
+ if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
+ startConfigService(context);
+ } else if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(action)) {
+ boolean airplaneModeOn = intent.getBooleanExtra("state", false);
+ if (!airplaneModeOn) {
+ startConfigService(context);
+ }
+ } else if (Telephony.Sms.Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION.equals(action) ||
+ Telephony.Sms.Intents.SMS_CB_RECEIVED_ACTION.equals(action)) {
+ // If 'privileged' is false, it means that the intent was delivered to the base
+ // no-permissions receiver class. If we get an SMS_CB_RECEIVED message that way, it
+ // means someone has tried to spoof the message by delivering it outside the normal
+ // permission-checked route, so we just ignore it.
+ if (privileged) {
+ intent.setClass(context, CellBroadcastAlertService.class);
+ context.startService(intent);
+ } else {
+ Log.e(TAG, "ignoring unprivileged action received " + action);
+ }
+ } else {
+ Log.w(TAG, "onReceive() unexpected action " + action);
+ }
+ }
+
+ /**
+ * Tell {@link CellBroadcastConfigService} to enable the CB channels.
+ * @param context the broadcast receiver context
+ */
+ static void startConfigService(Context context) {
+ if (phoneIsCdma()) {
+ Log.d(TAG, "CDMA phone detected; doing nothing");
+ } else {
+ Intent serviceIntent = new Intent(CellBroadcastConfigService.ACTION_ENABLE_CHANNELS,
+ null, context, CellBroadcastConfigService.class);
+ context.startService(serviceIntent);
+ }
+ }
+
+ /**
+ * @return true if the phone is a CDMA phone type
+ */
+ private static boolean phoneIsCdma() {
+ boolean isCdma = false;
+ try {
+ ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
+ if (phone != null) {
+ isCdma = (phone.getActivePhoneType() == TelephonyManager.PHONE_TYPE_CDMA);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "phone.getActivePhoneType() failed", e);
+ }
+ return isCdma;
+ }
+}
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastReceiverApp.java b/src/com/android/cellbroadcastreceiver/CellBroadcastReceiverApp.java
new file mode 100644
index 0000000..6fa5935
--- /dev/null
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastReceiverApp.java
@@ -0,0 +1,66 @@
+/*
+ * 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.app.Application;
+import android.content.SharedPreferences;
+import android.util.Log;
+import android.preference.PreferenceManager;
+
+import static com.android.cellbroadcastreceiver.CellBroadcastReceiver.DBG;
+
+/**
+ * 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;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
+ gCellBroadcastReceiverApp = this;
+ }
+
+ public static CellBroadcastReceiverApp getCellBroadcastReceiverApp() {
+ return gCellBroadcastReceiverApp;
+ }
+
+ // 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
+ }
+
+ // 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;
+ }
+}
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastSettings.java b/src/com/android/cellbroadcastreceiver/CellBroadcastSettings.java
new file mode 100644
index 0000000..fa5a742
--- /dev/null
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastSettings.java
@@ -0,0 +1,110 @@
+/*
+ * 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.res.Resources;
+import android.os.Bundle;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+
+/**
+ * Settings activity for the cell broadcast receiver.
+ */
+public class CellBroadcastSettings extends PreferenceActivity {
+
+ // Preference key for whether to enable emergency notifications (default enabled).
+ public static final String KEY_ENABLE_EMERGENCY_ALERTS = "enable_emergency_alerts";
+
+ // Duration of alert sound (in seconds).
+ public static final String KEY_ALERT_SOUND_DURATION = "alert_sound_duration";
+
+ // Default alert duration (in seconds).
+ public static final String ALERT_SOUND_DEFAULT_DURATION = "4";
+
+ // 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 amber alert messages (default is disabled).
+ public static final String KEY_ENABLE_CMAS_AMBER_ALERTS = "enable_cmas_amber_alerts";
+
+ // Whether to display CMAS monthly test messages (default is disabled).
+ public static final String KEY_ENABLE_CMAS_TEST_ALERTS = "enable_cmas_test_alerts";
+
+ // Preference category for Brazil specific settings.
+ public static final String KEY_CATEGORY_BRAZIL_SETTINGS = "category_brazil_settings";
+
+ // Preference key for whether to enable channel 50 notifications
+ // Enabled by default for phones sold in Brazil, otherwise this setting may be hidden.
+ public static final String KEY_ENABLE_CHANNEL_50_ALERTS = "enable_channel_50_alerts";
+
+ @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;
+ }
+ });
+
+ Preference.OnPreferenceChangeListener startConfigServiceListener =
+ new Preference.OnPreferenceChangeListener() {
+ public boolean onPreferenceChange(Preference pref, Object newValue) {
+ CellBroadcastReceiver.startConfigService(pref.getContext());
+ 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);
+ }
+ }
+}
diff --git a/src/com/android/cellbroadcastreceiver/PrivilegedCellBroadcastReceiver.java b/src/com/android/cellbroadcastreceiver/PrivilegedCellBroadcastReceiver.java
new file mode 100644
index 0000000..5ec97c0
--- /dev/null
+++ b/src/com/android/cellbroadcastreceiver/PrivilegedCellBroadcastReceiver.java
@@ -0,0 +1,35 @@
+/*
+ * 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.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * This class exists specifically to allow us to require permissions checks on SMS_RECEIVED
+ * broadcasts that are not applicable to other kinds of broadcast messages handled by the
+ * SmsReceiver base class.
+ */
+public class PrivilegedCellBroadcastReceiver extends CellBroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // Pass the message to the base class implementation, noting that it
+ // was permission-checked on the way in.
+ onReceiveWithPrivilege(context, intent, true);
+ }
+}
diff --git a/tests/Android.mk b/tests/Android.mk
new file mode 100644
index 0000000..001c367
--- /dev/null
+++ b/tests/Android.mk
@@ -0,0 +1,38 @@
+# Copyright 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.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+# Include all test java files.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+# Notice that we don't have to include the src files of Email because, by
+# running the tests using an instrumentation targeting Eamil, we
+# automatically get all of its classes loaded into our environment.
+
+LOCAL_PACKAGE_NAME := CellBroadcastReceiverTests
+
+# Apk must be signed with platform signature in order to send test broadcasts.
+LOCAL_CERTIFICATE := platform
+
+LOCAL_INSTRUMENTATION_FOR := CellBroadcastReceiver
+
+include $(BUILD_PACKAGE)
+
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
new file mode 100644
index 0000000..751e7ef
--- /dev/null
+++ b/tests/AndroidManifest.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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 name must be unique so suffix with "tests" so package loader doesn't ignore us -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cellbroadcastreceiver.tests">
+
+ <!-- Test Apk is signed with platform key in order to use this permission. -->
+ <uses-permission android:name="android.permission.BROADCAST_SMS"/>
+
+ <!-- We add an application tag here just so that we can indicate that
+ this package needs to link against the android.test library,
+ which is needed when building test cases. -->
+ <application android:label="@string/app_label">
+ <uses-library android:name="android.test.runner" />
+
+ <!-- Also include an activity for sending test messages. -->
+ <activity android:name="SendTestBroadcastActivity"
+ android:label="@string/app_label"
+ android:configChanges="orientation|keyboardHidden"
+ android:launchMode="singleTop">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ </application>
+
+ <!--
+ This declares that this app uses the instrumentation test runner targeting
+ the package of com.android.basicsmsreceiver. To run the tests use the command:
+ "runtest basicsmsreceiver"
+ -->
+ <instrumentation android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="com.android.cellbroadcastreceiver"
+ android:label="Tests for cellbroadcastreceiver."/>
+</manifest>
diff --git a/tests/res/layout/test_buttons.xml b/tests/res/layout/test_buttons.xml
new file mode 100644
index 0000000..5ea2ed1
--- /dev/null
+++ b/tests/res/layout/test_buttons.xml
@@ -0,0 +1,133 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- Buttons to send test broadcasts -->
+<ScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <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_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Button android:id="@+id/button_etws_cancel_type"
+ android:text="@string/button_etws_cancel_type"
+ android:layout_marginLeft="20dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Button android:id="@+id/button_etws_test_type"
+ android:text="@string/button_etws_test_type"
+ android:layout_marginLeft="20dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Button android:id="@+id/button_gsm_7bit_type"
+ android:text="@string/button_gsm_7bit_type"
+ android:layout_marginLeft="20dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Button android:id="@+id/button_gsm_7bit_umts_type"
+ android:text="@string/button_gsm_7bit_umts_type"
+ android:layout_marginLeft="20dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Button android:id="@+id/button_gsm_7bit_nopadding_type"
+ android:text="@string/button_gsm_7bit_nopadding_type"
+ android:layout_marginLeft="20dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Button android:id="@+id/button_gsm_7bit_nopadding_umts_type"
+ android:text="@string/button_gsm_7bit_nopadding_umts_type"
+ android:layout_marginLeft="20dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Button android:id="@+id/button_gsm_7bit_multipage_type"
+ android:text="@string/button_gsm_7bit_multipage_type"
+ android:layout_marginLeft="20dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Button android:id="@+id/button_gsm_7bit_multipage_umts_type"
+ android:text="@string/button_gsm_7bit_multipage_umts_type"
+ android:layout_marginLeft="20dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Button android:id="@+id/button_gsm_7bit_with_language_type"
+ android:text="@string/button_gsm_7bit_with_language_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"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Button android:id="@+id/button_gsm_ucs2_type"
+ android:text="@string/button_gsm_ucs2_type"
+ android:layout_marginLeft="20dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Button android:id="@+id/button_gsm_ucs2_umts_type"
+ android:text="@string/button_gsm_ucs2_umts_type"
+ android:layout_marginLeft="20dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Button android:id="@+id/button_gsm_ucs2_multipage_umts_type"
+ android:text="@string/button_gsm_ucs2_multipage_umts_type"
+ android:layout_marginLeft="20dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Button android:id="@+id/button_gsm_ucs2_with_language_type"
+ android:text="@string/button_gsm_ucs2_with_language_type"
+ android:layout_marginLeft="20dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Button android:id="@+id/button_gsm_ucs2_with_language_umts_type"
+ android:text="@string/button_gsm_ucs2_with_language_umts_type"
+ android:layout_marginLeft="20dp"
+ 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
new file mode 100644
index 0000000..16fbb7b
--- /dev/null
+++ b/tests/res/values/strings.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_label">Cell Broadcast Tests</string>
+ <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_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>
+ <string name="button_gsm_7bit_nopadding_umts_type">Send UMTS 7 Bit Full Length Broadcast</string>
+ <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_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>
+ <string name="button_gsm_ucs2_multipage_umts_type">Send UMTS UCS-2 Multi Page Broadcast</string>
+ <string name="button_gsm_ucs2_with_language_type">Send GSM UCS-2 With Language</string>
+ <string name="button_gsm_ucs2_with_language_umts_type">Send UMTS UCS-2 With Language</string>
+ <string name="button_delay_broadcast">Delay 5 seconds before sending</string>
+</resources>
diff --git a/tests/src/com/android/cellbroadcastreceiver/DialogSmsDisplayTests.java b/tests/src/com/android/cellbroadcastreceiver/DialogSmsDisplayTests.java
new file mode 100644
index 0000000..f7df27a
--- /dev/null
+++ b/tests/src/com/android/cellbroadcastreceiver/DialogSmsDisplayTests.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cellbroadcastreceiver;
+
+import java.io.UnsupportedEncodingException;
+
+import android.content.Intent;
+import android.provider.Telephony.Sms.Intents;
+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;
+
+/**
+ * Various instrumentation tests for CellBroadcastReceiver.
+ *
+ * To run this test: runtest cellbroadcastreceiver
+ * or: adb shell am instrument -w \
+ * com.android.cellbroadcastreceiver.tests/android.test.InstrumentationTestRunner
+ *
+ * TODO: write better test cases
+ *
+ */
+public class DialogSmsDisplayTests
+ extends ActivityInstrumentationTestCase2<CellBroadcastListActivity> {
+
+ public static final String ACTION_SMS_SENT =
+ "com.android.basicsmsreceiver.tests.SMS_SENT_ACTION";
+ private static String TAG = "DialogSmsDisplayTests";
+
+ private static final int DCS_7BIT_ENGLISH = 0x01;
+ private static final int DCS_16BIT_UCS2 = 0x48;
+
+ /* ETWS Test message including header */
+ private static final byte[] etwsMessageNormal = IccUtils.hexStringToBytes("000011001101" +
+ // Line 1 CRLFLine 2
+ "EA307DCA602557309707901F58310D0A5BAE57CE770C531790E85C716CBF3044573065B930675730" +
+ "9707767A751F30025F37304463FA308C306B5099304830664E0B30553044FF086C178C615E81FF09");
+
+ private static final byte[] etwsMessageCancel = IccUtils.hexStringToBytes("000011001101" +
+ // Line 1 CRLFLine 2
+ "EA307DCA602557309707901F5831002853D66D8800290D0A5148307B3069002800310030003A0035" +
+ "00320029306E7DCA602557309707901F5831309253D66D883057307E3059FF086C178C615E81FF09");
+
+ private static final byte[] etwsMessageTest = IccUtils.hexStringToBytes("000011031101" +
+ // Line 1 CRLFLine 2
+ "EA3030108A137DF430117DCA602557309707573058310D0A5BAE57CE770C531790E85C716CBF3044" +
+ "573065B9306757309707300263FA308C306B5099304830664E0B30553044FF086C178C615E81FF09");
+
+ public DialogSmsDisplayTests() {
+ super(CellBroadcastListActivity.class);
+ Log.i(TAG, "DialogSmsDisplayTests");
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ 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
+ 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");
+ return null;
+ } catch (UnsupportedEncodingException e) {
+ Log.e(TAG, "Unsupported encoding exception for UTF-16");
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ getActivity().sendOrderedBroadcast(intent,
+ "android.permission.RECEIVE_EMERGENCY_BROADCAST");
+ }
+}
diff --git a/tests/src/com/android/cellbroadcastreceiver/tests/SendTestBroadcastActivity.java b/tests/src/com/android/cellbroadcastreceiver/tests/SendTestBroadcastActivity.java
new file mode 100644
index 0000000..7727863
--- /dev/null
+++ b/tests/src/com/android/cellbroadcastreceiver/tests/SendTestBroadcastActivity.java
@@ -0,0 +1,290 @@
+/*
+ * 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.tests;
+
+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;
+import android.widget.CheckBox;
+
+/**
+ * Activity to send test cell broadcast messages from GUI.
+ */
+public class SendTestBroadcastActivity extends Activity {
+ private static String TAG = "SendTestBroadcastActivity";
+
+ /** Whether to delay before sending test message. */
+ private boolean mDelayBeforeSending;
+
+ /** Delay time before sending test message (when box is checked). */
+ private static final int DELAY_BEFORE_SENDING_MSEC = 5000;
+
+ /** Callback for sending test message after delay */
+ private OnClickListener mPendingButtonClick;
+
+ private Handler mDelayHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ // call the onClick() method again, passing null View.
+ // The callback will ignore mDelayBeforeSending when the View is null.
+ mPendingButtonClick.onClick(null);
+ }
+ };
+
+
+
+ /**
+ * Initialization of the Activity after it is first created. Must at least
+ * call {@link android.app.Activity#setContentView(int)} to
+ * describe what is to be displayed in the screen.
+ */
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.test_buttons);
+
+ /* Send an ETWS normal broadcast message to app. */
+ Button etwsNormalTypeButton = (Button) findViewById(R.id.button_etws_normal_type);
+ etwsNormalTypeButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ if (mDelayBeforeSending && v != null) {
+ mPendingButtonClick = this;
+ mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+ } else {
+ SendTestMessages.testSendEtwsMessageNormal(SendTestBroadcastActivity.this);
+ }
+ }
+ });
+
+ /* Send an ETWS cancel broadcast message to app. */
+ Button etwsCancelTypeButton = (Button) findViewById(R.id.button_etws_cancel_type);
+ etwsCancelTypeButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ if (mDelayBeforeSending && v != null) {
+ mPendingButtonClick = this;
+ mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+ } else {
+ SendTestMessages.testSendEtwsMessageCancel(SendTestBroadcastActivity.this);
+ }
+ }
+ });
+
+ /* Send an ETWS test broadcast message to app. */
+ Button etwsTestTypeButton = (Button) findViewById(R.id.button_etws_test_type);
+ etwsTestTypeButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ if (mDelayBeforeSending && v != null) {
+ mPendingButtonClick = this;
+ mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+ } else {
+ SendTestMessages.testSendEtwsMessageTest(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() {
+ public void onClick(View v) {
+ if (mDelayBeforeSending && v != null) {
+ mPendingButtonClick = this;
+ mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+ } else {
+ SendTestMessages.testSendMessage7bit(SendTestBroadcastActivity.this);
+ }
+ }
+ });
+
+ /* Send a UMTS 7-bit broadcast message to app. */
+ Button gsm7bitUmtsTypeButton = (Button) findViewById(R.id.button_gsm_7bit_umts_type);
+ gsm7bitUmtsTypeButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ if (mDelayBeforeSending && v != null) {
+ mPendingButtonClick = this;
+ mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+ } else {
+ SendTestMessages.testSendMessage7bitUmts(SendTestBroadcastActivity.this);
+ }
+ }
+ });
+
+ /* Send a GSM 7-bit no padding broadcast message to app. */
+ Button gsm7bitNoPaddingButton = (Button) findViewById(R.id.button_gsm_7bit_nopadding_type);
+ gsm7bitNoPaddingButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ if (mDelayBeforeSending && v != null) {
+ mPendingButtonClick = this;
+ mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+ } else {
+ SendTestMessages.testSendMessage7bitNoPadding(SendTestBroadcastActivity.this);
+ }
+ }
+ });
+
+ /* Send a UMTS 7-bit no padding broadcast message to app. */
+ Button gsm7bitNoPaddingUmtsTypeButton =
+ (Button) findViewById(R.id.button_gsm_7bit_nopadding_umts_type);
+ gsm7bitNoPaddingUmtsTypeButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ if (mDelayBeforeSending && v != null) {
+ mPendingButtonClick = this;
+ mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+ } else {
+ SendTestMessages.testSendMessage7bitNoPaddingUmts(SendTestBroadcastActivity.this);
+ }
+ }
+ });
+
+ /* Send a UMTS 7-bit multi-page broadcast message to app. */
+ Button gsm7bitMultipageButton =
+ (Button) findViewById(R.id.button_gsm_7bit_multipage_type);
+ gsm7bitMultipageButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ if (mDelayBeforeSending && v != null) {
+ mPendingButtonClick = this;
+ mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+ } else {
+ SendTestMessages.testSendMessage7bitMultipageGsm(SendTestBroadcastActivity.this);
+ }
+ }
+ });
+
+ /* Send a UMTS 7-bit multi-page broadcast message to app. */
+ Button gsm7bitMultipageUmtsButton =
+ (Button) findViewById(R.id.button_gsm_7bit_multipage_umts_type);
+ gsm7bitMultipageUmtsButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ if (mDelayBeforeSending && v != null) {
+ mPendingButtonClick = this;
+ mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+ } else {
+ SendTestMessages.testSendMessage7bitMultipageUmts(SendTestBroadcastActivity.this);
+ }
+ }
+ });
+
+ /* Send a GSM 7-bit broadcast message with language to app. */
+ Button gsm7bitWithLanguageButton =
+ (Button) findViewById(R.id.button_gsm_7bit_with_language_type);
+ gsm7bitWithLanguageButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ if (mDelayBeforeSending && v != null) {
+ mPendingButtonClick = this;
+ mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+ } else {
+ SendTestMessages.testSendMessage7bitWithLanguage(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);
+ gsm7bitWithLanguageUmtsButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ if (mDelayBeforeSending && v != null) {
+ mPendingButtonClick = this;
+ mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+ } else {
+ SendTestMessages.testSendMessage7bitWithLanguageInBodyUmts(SendTestBroadcastActivity.this);
+ }
+ }
+ });
+
+ /* Send a GSM UCS-2 broadcast message to app. */
+ Button gsmUcs2TypeButton = (Button) findViewById(R.id.button_gsm_ucs2_type);
+ gsmUcs2TypeButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ if (mDelayBeforeSending && v != null) {
+ mPendingButtonClick = this;
+ mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+ } else {
+ SendTestMessages.testSendMessageUcs2(SendTestBroadcastActivity.this);
+ }
+ }
+ });
+
+ /* Send a UMTS UCS-2 broadcast message to app. */
+ Button gsmUcs2UmtsTypeButton = (Button) findViewById(R.id.button_gsm_ucs2_umts_type);
+ gsmUcs2UmtsTypeButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ if (mDelayBeforeSending && v != null) {
+ mPendingButtonClick = this;
+ mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+ } else {
+ SendTestMessages.testSendMessageUcs2Umts(SendTestBroadcastActivity.this);
+ }
+ }
+ });
+
+ /* Send a UMTS UCS-2 multipage broadcast message to app. */
+ Button gsmUcs2MultipageUmtsTypeButton =
+ (Button) findViewById(R.id.button_gsm_ucs2_multipage_umts_type);
+ gsmUcs2MultipageUmtsTypeButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ if (mDelayBeforeSending && v != null) {
+ mPendingButtonClick = this;
+ mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+ } else {
+ SendTestMessages.testSendMessageUcs2MultipageUmts(SendTestBroadcastActivity.this);
+ }
+ }
+ });
+
+ /* Send a GSM UCS-2 broadcast message with language to app. */
+ Button gsmUcs2WithLanguageTypeButton =
+ (Button) findViewById(R.id.button_gsm_ucs2_with_language_type);
+ gsmUcs2WithLanguageTypeButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ if (mDelayBeforeSending && v != null) {
+ mPendingButtonClick = this;
+ mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+ } else {
+ SendTestMessages.testSendMessageUcs2WithLanguageInBody(SendTestBroadcastActivity.this);
+ }
+ }
+ });
+
+ /* Send a UMTS UCS-2 broadcast message with language to app. */
+ Button gsmUcs2WithLanguageUmtsTypeButton =
+ (Button) findViewById(R.id.button_gsm_ucs2_with_language_umts_type);
+ gsmUcs2WithLanguageUmtsTypeButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ if (mDelayBeforeSending && v != null) {
+ mPendingButtonClick = this;
+ mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+ } else {
+ SendTestMessages.testSendMessageUcs2WithLanguageUmts(SendTestBroadcastActivity.this);
+ }
+ }
+ });
+
+ /* Update boolean to delay before sending when box is checked. */
+ final CheckBox delayCheckbox = (CheckBox) findViewById(R.id.button_delay_broadcast);
+ delayCheckbox.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ mDelayBeforeSending = delayCheckbox.isChecked();
+ }
+ });
+ }
+}
diff --git a/tests/src/com/android/cellbroadcastreceiver/tests/SendTestMessages.java b/tests/src/com/android/cellbroadcastreceiver/tests/SendTestMessages.java
new file mode 100644
index 0000000..893d03a
--- /dev/null
+++ b/tests/src/com/android/cellbroadcastreceiver/tests/SendTestMessages.java
@@ -0,0 +1,553 @@
+/*
+ * 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.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.util.Log;
+
+import com.android.internal.telephony.EncodeException;
+import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.IccUtils;
+
+/**
+ * Send test messages.
+ */
+public class SendTestMessages {
+
+ private static String TAG = "SendTestMessages";
+
+ private static final int DCS_7BIT_ENGLISH = 0x01;
+ private static final int DCS_16BIT_UCS2 = 0x48;
+
+ /* ETWS Test message including header */
+ private static final byte[] etwsMessageNormal = IccUtils.hexStringToBytes("000011001101" +
+ "0D0A5BAE57CE770C531790E85C716CBF3044573065B930675730" +
+ "9707767A751F30025F37304463FA308C306B5099304830664E0B30553044FF086C178C615E81FF09" +
+ "0000000000000000000000000000");
+
+ private static final byte[] etwsMessageCancel = IccUtils.hexStringToBytes("000011001101" +
+ "0D0A5148307B3069002800310030003A0035" +
+ "00320029306E7DCA602557309707901F5831309253D66D883057307E3059FF086C178C615E81FF09" +
+ "00000000000000000000000000000000000000000000");
+
+ private static final byte[] etwsMessageTest = IccUtils.hexStringToBytes("000011031101" +
+ "0D0A5BAE57CE770C531790E85C716CBF3044" +
+ "573065B9306757309707300263FA308C306B5099304830664E0B30553044FF086C178C615E81FF09" +
+ "00000000000000000000000000000000000000000000");
+
+ private static final byte[] gsm7BitTest = {
+ (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x40, (byte)0x11, (byte)0x41,
+ (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91, (byte)0xCB, (byte)0xE6,
+ (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07, (byte)0x85, (byte)0xD9, (byte)0x70,
+ (byte)0x74, (byte)0x58, (byte)0x5C, (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5,
+ (byte)0xF9, (byte)0x3C, (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69,
+ (byte)0x3A, (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9,
+ (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9, (byte)0x75,
+ (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93, (byte)0xC9, (byte)0x69,
+ (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00
+ };
+
+ private static final byte[] gsm7BitTestUmts = {
+ (byte)0x01, (byte)0x00, (byte)0x32, (byte)0xC0, (byte)0x00, (byte)0x40,
+
+ (byte)0x01,
+
+ (byte)0x41, (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91,
+ (byte)0xCB, (byte)0xE6, (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07,
+ (byte)0x85, (byte)0xD9, (byte)0x70, (byte)0x74, (byte)0x58, (byte)0x5C,
+ (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5, (byte)0xF9, (byte)0x3C,
+ (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69, (byte)0x3A,
+ (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9,
+ (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9,
+ (byte)0x75, (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93,
+ (byte)0xC9, (byte)0x69, (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68,
+ (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1,
+ (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3,
+ (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46,
+ (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00,
+
+ (byte)0x34
+ };
+
+ private static final byte[] gsm7BitTestMultipageUmts = {
+ (byte)0x01, (byte)0x00, (byte)0x01, (byte)0xC0, (byte)0x00, (byte)0x40,
+
+ (byte)0x02,
+
+ (byte)0xC6, (byte)0xB4, (byte)0x7C, (byte)0x4E, (byte)0x07, (byte)0xC1,
+ (byte)0xC3, (byte)0xE7, (byte)0xF2, (byte)0xAA, (byte)0xD1, (byte)0x68,
+ (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1,
+ (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3,
+ (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46,
+ (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A,
+ (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34,
+ (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68,
+ (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1,
+ (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3,
+ (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46,
+ (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00,
+
+ (byte)0x0A,
+
+ (byte)0xD3, (byte)0xF2, (byte)0xF8, (byte)0xED, (byte)0x26, (byte)0x83,
+ (byte)0xE0, (byte)0xE1, (byte)0x73, (byte)0xB9, (byte)0xD1, (byte)0x68,
+ (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1,
+ (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3,
+ (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46,
+ (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A,
+ (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34,
+ (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68,
+ (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1,
+ (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3,
+ (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46,
+ (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00,
+
+ (byte)0x0A
+ };
+
+ private static final byte[] gsm7BitTestMultipage1 = {
+ (byte)0x01, (byte)0x00, (byte)0x01, (byte)0xC0, (byte)0x00, (byte)0x40,
+ (byte)0xC6, (byte)0xB4, (byte)0x7C, (byte)0x4E, (byte)0x07, (byte)0xC1,
+ (byte)0xC3, (byte)0xE7, (byte)0xF2, (byte)0xAA, (byte)0xD1, (byte)0x68,
+ (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1,
+ (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3,
+ (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46,
+ (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A,
+ (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34,
+ (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68,
+ (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1,
+ (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3,
+ (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46,
+ (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00
+ };
+
+ private static final byte[] gsm7BitTestMultipage2 = {
+ (byte)0x01, (byte)0x00, (byte)0x01, (byte)0xC0, (byte)0x00, (byte)0x40,
+ (byte)0xD3, (byte)0xF2, (byte)0xF8, (byte)0xED, (byte)0x26, (byte)0x83,
+ (byte)0xE0, (byte)0xE1, (byte)0x73, (byte)0xB9, (byte)0xD1, (byte)0x68,
+ (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1,
+ (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3,
+ (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46,
+ (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A,
+ (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34,
+ (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68,
+ (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1,
+ (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3,
+ (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46,
+ (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00
+ };
+
+ private static final byte[] gsm7BitTestNoPadding = {
+ (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x40, (byte)0x11, (byte)0x41,
+ (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91, (byte)0xCB, (byte)0xE6,
+ (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07, (byte)0x85, (byte)0xD9, (byte)0x70,
+ (byte)0x74, (byte)0x58, (byte)0x5C, (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5,
+ (byte)0xF9, (byte)0x3C, (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xC4, (byte)0xE5,
+ (byte)0xB4, (byte)0xFB, (byte)0x0C, (byte)0x2A, (byte)0xE3, (byte)0xC3, (byte)0x63,
+ (byte)0x3A, (byte)0x3B, (byte)0x0F, (byte)0xCA, (byte)0xCD, (byte)0x40, (byte)0x63,
+ (byte)0x74, (byte)0x58, (byte)0x1E, (byte)0x1E, (byte)0xD3, (byte)0xCB, (byte)0xF2,
+ (byte)0x39, (byte)0x88, (byte)0xFD, (byte)0x76, (byte)0x9F, (byte)0x59, (byte)0xA0,
+ (byte)0x76, (byte)0x39, (byte)0xEC, (byte)0x4E, (byte)0xBB, (byte)0xCF, (byte)0x20,
+ (byte)0x3A, (byte)0xBA, (byte)0x2C, (byte)0x2F, (byte)0x83, (byte)0xD2, (byte)0x73,
+ (byte)0x90, (byte)0xFB, (byte)0x0D, (byte)0x82, (byte)0x87, (byte)0xC9, (byte)0xE4,
+ (byte)0xB4, (byte)0xFB, (byte)0x1C, (byte)0x02
+ };
+
+ private static final byte[] gsm7BitTestNoPaddingUmts = {
+ (byte)0x01, (byte)0x00, (byte)0x32, (byte)0xC0, (byte)0x00, (byte)0x40,
+
+ (byte)0x01,
+
+ (byte)0x41, (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91,
+ (byte)0xCB, (byte)0xE6, (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07,
+ (byte)0x85, (byte)0xD9, (byte)0x70, (byte)0x74, (byte)0x58, (byte)0x5C,
+ (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5, (byte)0xF9, (byte)0x3C,
+ (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xC4, (byte)0xE5, (byte)0xB4,
+ (byte)0xFB, (byte)0x0C, (byte)0x2A, (byte)0xE3, (byte)0xC3, (byte)0x63,
+ (byte)0x3A, (byte)0x3B, (byte)0x0F, (byte)0xCA, (byte)0xCD, (byte)0x40,
+ (byte)0x63, (byte)0x74, (byte)0x58, (byte)0x1E, (byte)0x1E, (byte)0xD3,
+ (byte)0xCB, (byte)0xF2, (byte)0x39, (byte)0x88, (byte)0xFD, (byte)0x76,
+ (byte)0x9F, (byte)0x59, (byte)0xA0, (byte)0x76, (byte)0x39, (byte)0xEC,
+ (byte)0x4E, (byte)0xBB, (byte)0xCF, (byte)0x20, (byte)0x3A, (byte)0xBA,
+ (byte)0x2C, (byte)0x2F, (byte)0x83, (byte)0xD2, (byte)0x73, (byte)0x90,
+ (byte)0xFB, (byte)0x0D, (byte)0x82, (byte)0x87, (byte)0xC9, (byte)0xE4,
+ (byte)0xB4, (byte)0xFB, (byte)0x1C, (byte)0x02,
+
+ (byte)0x52
+ };
+
+ private static final byte[] gsm7BitTestWithLanguage = {
+ (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x04, (byte)0x11, (byte)0x41,
+ (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91, (byte)0xCB, (byte)0xE6,
+ (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07, (byte)0x85, (byte)0xD9, (byte)0x70,
+ (byte)0x74, (byte)0x58, (byte)0x5C, (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5,
+ (byte)0xF9, (byte)0x3C, (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69,
+ (byte)0x3A, (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9,
+ (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9, (byte)0x75,
+ (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93, (byte)0xC9, (byte)0x69,
+ (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00
+ };
+
+ private static final byte[] gsm7BitTestWithLanguageInBody = {
+ (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x10, (byte)0x11, (byte)0x73,
+ (byte)0x7B, (byte)0x23, (byte)0x08, (byte)0x3A, (byte)0x4E, (byte)0x9B, (byte)0x20,
+ (byte)0x72, (byte)0xD9, (byte)0x1C, (byte)0xAE, (byte)0xB3, (byte)0xE9, (byte)0xA0,
+ (byte)0x30, (byte)0x1B, (byte)0x8E, (byte)0x0E, (byte)0x8B, (byte)0xCB, (byte)0x74,
+ (byte)0x50, (byte)0xBB, (byte)0x3C, (byte)0x9F, (byte)0x87, (byte)0xCF, (byte)0x65,
+ (byte)0xD0, (byte)0x3D, (byte)0x4D, (byte)0x47, (byte)0x83, (byte)0xC6, (byte)0x61,
+ (byte)0xB9, (byte)0x3C, (byte)0x1D, (byte)0x3E, (byte)0x97, (byte)0x41, (byte)0xF2,
+ (byte)0x32, (byte)0xBD, (byte)0x2E, (byte)0x77, (byte)0x83, (byte)0xE0, (byte)0x61,
+ (byte)0x32, (byte)0x39, (byte)0xED, (byte)0x3E, (byte)0x37, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00
+ };
+
+ private static final byte[] gsm7BitTestWithLanguageInBodyUmts = {
+ (byte)0x01, (byte)0x00, (byte)0x32, (byte)0xC0, (byte)0x00, (byte)0x10,
+
+ (byte)0x01,
+
+ (byte)0x73, (byte)0x7B, (byte)0x23, (byte)0x08, (byte)0x3A, (byte)0x4E,
+ (byte)0x9B, (byte)0x20, (byte)0x72, (byte)0xD9, (byte)0x1C, (byte)0xAE,
+ (byte)0xB3, (byte)0xE9, (byte)0xA0, (byte)0x30, (byte)0x1B, (byte)0x8E,
+ (byte)0x0E, (byte)0x8B, (byte)0xCB, (byte)0x74, (byte)0x50, (byte)0xBB,
+ (byte)0x3C, (byte)0x9F, (byte)0x87, (byte)0xCF, (byte)0x65, (byte)0xD0,
+ (byte)0x3D, (byte)0x4D, (byte)0x47, (byte)0x83, (byte)0xC6, (byte)0x61,
+ (byte)0xB9, (byte)0x3C, (byte)0x1D, (byte)0x3E, (byte)0x97, (byte)0x41,
+ (byte)0xF2, (byte)0x32, (byte)0xBD, (byte)0x2E, (byte)0x77, (byte)0x83,
+ (byte)0xE0, (byte)0x61, (byte)0x32, (byte)0x39, (byte)0xED, (byte)0x3E,
+ (byte)0x37, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1,
+ (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3,
+ (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46,
+ (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
+ (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00,
+
+ (byte)0x37
+ };
+
+ private static final byte[] gsmUcs2Test = {
+ (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x48, (byte)0x11, (byte)0x00,
+ (byte)0x41, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x55, (byte)0x00, (byte)0x43,
+ (byte)0x00, (byte)0x53, (byte)0x00, (byte)0x32, (byte)0x00, (byte)0x20, (byte)0x00,
+ (byte)0x6D, (byte)0x00, (byte)0x65, (byte)0x00, (byte)0x73, (byte)0x00, (byte)0x73,
+ (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x67, (byte)0x00, (byte)0x65, (byte)0x00,
+ (byte)0x20, (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x6F, (byte)0x00, (byte)0x6E,
+ (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x69, (byte)0x00,
+ (byte)0x6E, (byte)0x00, (byte)0x69, (byte)0x00, (byte)0x6E, (byte)0x00, (byte)0x67,
+ (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x20, (byte)0x04,
+ (byte)0x34, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x68,
+ (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x72, (byte)0x00, (byte)0x61, (byte)0x00,
+ (byte)0x63, (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x65, (byte)0x00, (byte)0x72,
+ (byte)0x00, (byte)0x0D, (byte)0x00, (byte)0x0D
+ };
+
+ private static final byte[] gsmUcs2TestUmts = {
+ (byte)0x01, (byte)0x00, (byte)0x32, (byte)0xC0, (byte)0x00, (byte)0x48,
+
+ (byte)0x01,
+
+ (byte)0x00, (byte)0x41, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x55,
+ (byte)0x00, (byte)0x43, (byte)0x00, (byte)0x53, (byte)0x00, (byte)0x32,
+ (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x6D, (byte)0x00, (byte)0x65,
+ (byte)0x00, (byte)0x73, (byte)0x00, (byte)0x73, (byte)0x00, (byte)0x61,
+ (byte)0x00, (byte)0x67, (byte)0x00, (byte)0x65, (byte)0x00, (byte)0x20,
+ (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x6F, (byte)0x00, (byte)0x6E,
+ (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x69,
+ (byte)0x00, (byte)0x6E, (byte)0x00, (byte)0x69, (byte)0x00, (byte)0x6E,
+ (byte)0x00, (byte)0x67, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x61,
+ (byte)0x00, (byte)0x20, (byte)0x04, (byte)0x34, (byte)0x00, (byte)0x20,
+ (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x68, (byte)0x00, (byte)0x61,
+ (byte)0x00, (byte)0x72, (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x63,
+ (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x65, (byte)0x00, (byte)0x72,
+ (byte)0x00, (byte)0x0D, (byte)0x00, (byte)0x0D,
+
+ (byte)0x4E
+ };
+
+ private static final byte[] gsmUcs2TestMultipageUmts = {
+ (byte)0x01, (byte)0x00, (byte)0x32, (byte)0xC0, (byte)0x00, (byte)0x48,
+
+ (byte)0x02,
+
+ (byte)0x00, (byte)0x41, (byte)0x00, (byte)0x41, (byte)0x00, (byte)0x41,
+ (byte)0x00, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+
+ (byte)0x06,
+
+ (byte)0x00, (byte)0x42, (byte)0x00, (byte)0x42, (byte)0x00, (byte)0x42,
+ (byte)0x00, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+ (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
+
+ (byte)0x06
+ };
+
+ private static final byte[] gsmUcs2TestWithLanguageInBody = {
+ (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x11, (byte)0x11, (byte)0x78,
+ (byte)0x3C, (byte)0x00, (byte)0x41, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x55,
+ (byte)0x00, (byte)0x43, (byte)0x00, (byte)0x53, (byte)0x00, (byte)0x32, (byte)0x00,
+ (byte)0x20, (byte)0x00, (byte)0x6D, (byte)0x00, (byte)0x65, (byte)0x00, (byte)0x73,
+ (byte)0x00, (byte)0x73, (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x67, (byte)0x00,
+ (byte)0x65, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x6F,
+ (byte)0x00, (byte)0x6E, (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x61, (byte)0x00,
+ (byte)0x69, (byte)0x00, (byte)0x6E, (byte)0x00, (byte)0x69, (byte)0x00, (byte)0x6E,
+ (byte)0x00, (byte)0x67, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x61, (byte)0x00,
+ (byte)0x20, (byte)0x04, (byte)0x34, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x63,
+ (byte)0x00, (byte)0x68, (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x72, (byte)0x00,
+ (byte)0x61, (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x65,
+ (byte)0x00, (byte)0x72, (byte)0x00, (byte)0x0D
+ };
+
+ private static final byte[] gsmUcs2TestWithLanguageInBodyUmts = {
+ (byte)0x01, (byte)0x00, (byte)0x32, (byte)0xC0, (byte)0x00, (byte)0x11,
+
+ (byte)0x01,
+
+ (byte)0x78, (byte)0x3C, (byte)0x00, (byte)0x41, (byte)0x00, (byte)0x20,
+ (byte)0x00, (byte)0x55, (byte)0x00, (byte)0x43, (byte)0x00, (byte)0x53,
+ (byte)0x00, (byte)0x32, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x6D,
+ (byte)0x00, (byte)0x65, (byte)0x00, (byte)0x73, (byte)0x00, (byte)0x73,
+ (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x67, (byte)0x00, (byte)0x65,
+ (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x6F,
+ (byte)0x00, (byte)0x6E, (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x61,
+ (byte)0x00, (byte)0x69, (byte)0x00, (byte)0x6E, (byte)0x00, (byte)0x69,
+ (byte)0x00, (byte)0x6E, (byte)0x00, (byte)0x67, (byte)0x00, (byte)0x20,
+ (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x20, (byte)0x04, (byte)0x34,
+ (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x68,
+ (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x72, (byte)0x00, (byte)0x61,
+ (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x65,
+ (byte)0x00, (byte)0x72, (byte)0x00, (byte)0x0D,
+
+ (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
+ 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");
+ return null;
+ } catch (UnsupportedEncodingException e) {
+ Log.e(TAG, "Unsupported encoding exception for UTF-16");
+ 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);
+ 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);
+ 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);
+ 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);
+ activity.sendOrderedBroadcast(intent, "android.permission.RECEIVE_SMS");
+ }
+
+ public static void testSendMessage7bitMultipageGsm(Activity activity) {
+ Intent intent = new Intent(Intents.SMS_CB_RECEIVED_ACTION);
+ byte[][] pdus = new byte[2][];
+ pdus[0] = gsm7BitTestMultipage1;
+ pdus[1] = gsm7BitTestMultipage2;
+ intent.putExtra("pdus", 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ activity.sendOrderedBroadcast(intent,
+ "android.permission.RECEIVE_EMERGENCY_BROADCAST");
+ }
+}