summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorandroid-build-team Robot <android-build-team-robot@google.com>2019-11-11 21:18:31 +0000
committerandroid-build-team Robot <android-build-team-robot@google.com>2019-11-11 21:18:31 +0000
commit2b8514135718c9314baa7a66a13972fb4e82619d (patch)
treeeeabb840b9abe143cb6aae3ef519f1609d254c51
parentebf384ebdc42932a924d5f42e5a4fc281cee436a (diff)
parente6a30d8437bb9110f75b3ddb196a54f800b7d98c (diff)
downloadplatform_packages_apps_CellBroadcastReceiver-android10-mainline-resolv-release.tar.gz
platform_packages_apps_CellBroadcastReceiver-android10-mainline-resolv-release.tar.bz2
platform_packages_apps_CellBroadcastReceiver-android10-mainline-resolv-release.zip
Snap for 6001391 from e6a30d8437bb9110f75b3ddb196a54f800b7d98c to qt-aml-resolv-releaseandroid10-mainline-resolv-release
Change-Id: Iae88e302d3970068f7194fb881fc7083bbf63851
-rw-r--r--Android.bp4
-rw-r--r--AndroidManifest.xml13
-rw-r--r--AndroidManifest_Platform.xml11
-rw-r--r--res/values-mcc226/config.xml (renamed from res/values-mcc313-mnc100/config.xml)12
l---------res/values-mcc312-mnc6702
l---------res/values-mcc313-mnc1001
l---------res/values-mcc313-mnc1102
l---------res/values-mcc313-mnc1202
l---------res/values-mcc313-mnc1302
l---------res/values-mcc313-mnc1402
l---------res/values-mcc3141
l---------res/values-mcc314/config.xml1
l---------res/values-mcc3151
l---------res/values-mcc315/config.xml1
l---------res/values-mcc3161
l---------res/values-mcc316/config.xml1
-rw-r--r--res/values-mcc424/strings.xml3
-rw-r--r--res/values-mcc530/config.xml4
-rw-r--r--res/values-mcc530/strings.xml8
-rw-r--r--res/values-mcc716/config.xml3
-rw-r--r--res/values/config.xml9
-rw-r--r--res/values/strings.xml6
-rw-r--r--res/xml/preferences.xml7
-rw-r--r--src/com/android/cellbroadcastreceiver/CbGeoUtils.java316
-rw-r--r--src/com/android/cellbroadcastreceiver/CellBroadcastAlertAudio.java136
-rw-r--r--src/com/android/cellbroadcastreceiver/CellBroadcastAlertDialog.java51
-rw-r--r--src/com/android/cellbroadcastreceiver/CellBroadcastAlertService.java209
-rw-r--r--src/com/android/cellbroadcastreceiver/CellBroadcastAreaInfoReceiver.java1
-rw-r--r--src/com/android/cellbroadcastreceiver/CellBroadcastChannelManager.java152
-rw-r--r--src/com/android/cellbroadcastreceiver/CellBroadcastConfigService.java222
-rw-r--r--src/com/android/cellbroadcastreceiver/CellBroadcastContentProvider.java2
-rw-r--r--src/com/android/cellbroadcastreceiver/CellBroadcastCursorAdapter.java1
-rw-r--r--src/com/android/cellbroadcastreceiver/CellBroadcastDatabaseHelper.java9
-rw-r--r--src/com/android/cellbroadcastreceiver/CellBroadcastHandler.java442
-rw-r--r--src/com/android/cellbroadcastreceiver/CellBroadcastListActivity.java1
-rw-r--r--src/com/android/cellbroadcastreceiver/CellBroadcastListItem.java1
-rw-r--r--src/com/android/cellbroadcastreceiver/CellBroadcastMessage.java462
-rw-r--r--src/com/android/cellbroadcastreceiver/CellBroadcastReceiver.java11
-rw-r--r--src/com/android/cellbroadcastreceiver/CellBroadcastReceiverApp.java1
-rw-r--r--src/com/android/cellbroadcastreceiver/CellBroadcastResources.java55
-rw-r--r--src/com/android/cellbroadcastreceiver/CellBroadcastSearchIndexableProvider.java58
-rw-r--r--src/com/android/cellbroadcastreceiver/CellBroadcastSettings.java104
-rw-r--r--src/com/android/cellbroadcastreceiver/DefaultCellBroadcastService.java48
-rw-r--r--src/com/android/cellbroadcastreceiver/GsmCellBroadcastHandler.java404
-rw-r--r--src/com/android/cellbroadcastreceiver/GsmSmsCbMessage.java509
-rw-r--r--src/com/android/cellbroadcastreceiver/IState.java73
-rw-r--r--src/com/android/cellbroadcastreceiver/SmsCbHeader.java596
-rw-r--r--src/com/android/cellbroadcastreceiver/State.java80
-rw-r--r--src/com/android/cellbroadcastreceiver/StateMachine.java2183
-rw-r--r--src/com/android/cellbroadcastreceiver/WakeLockStateMachine.java249
-rw-r--r--tests/testapp/src/com/android/cellbroadcastreceiver/tests/SendCdmaCmasMessages.java2
-rw-r--r--tests/testapp/src/com/android/cellbroadcastreceiver/tests/SendGsmCmasMessages.java2
-rw-r--r--tests/testapp/src/com/android/cellbroadcastreceiver/tests/SendTestMessages.java2
-rw-r--r--tests/unit/AndroidTest.xml2
-rw-r--r--tests/unit/src/com/android/cellbroadcastreceiver/CellBroadcastAlertDialogTest.java1
-rw-r--r--tests/unit/src/com/android/cellbroadcastreceiver/CellBroadcastAlertServiceTest.java13
-rw-r--r--tests/unit/src/com/android/cellbroadcastreceiver/CellBroadcastChannelManagerTest.java9
-rw-r--r--tests/unit/src/com/android/cellbroadcastreceiver/CellBroadcastConfigServiceTest.java80
-rw-r--r--tests/unit/src/com/android/cellbroadcastreceiver/CellBroadcastServiceTestCase.java6
-rw-r--r--tests/unit/src/com/android/cellbroadcastreceiver/CellBroadcastTest.java5
60 files changed, 1112 insertions, 5483 deletions
diff --git a/Android.bp b/Android.bp
index e6157e290..cff6ab8b1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -4,10 +4,8 @@ java_defaults {
min_sdk_version: "29",
platform_apis: true,
privileged: true,
- // TODO remove framework-cellbroadcast-shared-srcs once move service to different repo
srcs: [
"src/**/*.java",
- ":framework-cellbroadcast-shared-srcs",
],
libs: ["telephony-common"],
static_libs: [
@@ -39,4 +37,4 @@ android_app {
// CellBroadcastAppPlatform is a replacement for CellBroadcastApp
overrides: ["CellBroadcastApp"],
manifest: "AndroidManifest_Platform.xml",
-} \ No newline at end of file
+}
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 8fdbbba26..42b72beb1 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -18,7 +18,6 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.cellbroadcastreceiver"
- android:sharedUserId="android.uid.phone"
android:versionCode="300000000"
android:versionName="R-initial">
@@ -34,7 +33,6 @@
<uses-permission android:name="android.permission.MANAGE_USERS" />
<uses-permission android:name="android.permission.DEVICE_POWER" />
<uses-permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND" />
- <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-sdk android:minSdkVersion="21"/>
@@ -49,15 +47,6 @@
<meta-data android:name="com.google.android.backup.api_key"
android:value="AEdPqrEAAAAI2_Lb4sDI0e0twL-kf6GIqXpZIfrR0OhnM1pNJQ" />
- <service android:name="DefaultCellBroadcastService"
- android:process="com.android.phone"
- android:exported="true"
- android:permission="android.permission.BIND_CELL_BROADCAST_SERVICE">
- <intent-filter>
- <action android:name="android.telephony.CellBroadcastService" />
- </intent-filter>
- </service>
-
<service android:name="CellBroadcastAlertAudio"
android:exported="false" />
@@ -70,8 +59,10 @@
<service android:name="CellBroadcastAlertReminder"
android:exported="false" />
+ <!-- Export provider for AT&T Device and Network Reset-->
<provider android:name="CellBroadcastContentProvider"
android:authorities="cellbroadcasts"
+ android:exported="true"
android:readPermission="android.permission.READ_CELL_BROADCASTS" />
<activity android:name="CellBroadcastListActivity"
diff --git a/AndroidManifest_Platform.xml b/AndroidManifest_Platform.xml
index 76beac697..7cdd67d72 100644
--- a/AndroidManifest_Platform.xml
+++ b/AndroidManifest_Platform.xml
@@ -17,7 +17,6 @@
*/
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- android:sharedUserId="android.uid.phone"
package="com.android.cellbroadcastreceiver">
<original-package android:name="com.android.cellbroadcastreceiver" />
@@ -32,7 +31,6 @@
<uses-permission android:name="android.permission.MANAGE_USERS" />
<uses-permission android:name="android.permission.DEVICE_POWER" />
<uses-permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND" />
- <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-sdk android:minSdkVersion="21"/>
@@ -47,15 +45,6 @@
<meta-data android:name="com.google.android.backup.api_key"
android:value="AEdPqrEAAAAI2_Lb4sDI0e0twL-kf6GIqXpZIfrR0OhnM1pNJQ" />
- <service android:name="DefaultCellBroadcastService"
- android:process="com.android.phone"
- android:exported="true"
- android:permission="android.permission.BIND_CELL_BROADCAST_SERVICE">
- <intent-filter>
- <action android:name="android.telephony.CellBroadcastService" />
- </intent-filter>
- </service>
-
<service android:name="CellBroadcastAlertAudio"
android:exported="false" />
diff --git a/res/values-mcc313-mnc100/config.xml b/res/values-mcc226/config.xml
index 423eaea57..4dee02eb0 100644
--- a/res/values-mcc313-mnc100/config.xml
+++ b/res/values-mcc226/config.xml
@@ -13,12 +13,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<resources>
- <!-- Whether to compare message body when performing message duplicate dection -->
- <bool name="duplicate_compare_body">true</bool>
- <!-- Whether to reset alert message duplicate detection after toggling airplane mode -->
- <bool name="reset_duplicate_detection_on_airplane_mode">true</bool>
- <!-- Show timestamp of the alert -->
- <bool name="show_date_time_title">true</bool>
-</resources>
+<resources>
+ <!-- Whether to display presidential alert in the settings -->
+ <bool name="show_presidential_alerts_in_settings">true</bool>
+</resources> \ No newline at end of file
diff --git a/res/values-mcc312-mnc670 b/res/values-mcc312-mnc670
index 2d21ee5ba..78e65039e 120000
--- a/res/values-mcc312-mnc670
+++ b/res/values-mcc312-mnc670
@@ -1 +1 @@
-values-mcc313-mnc100 \ No newline at end of file
+values-mcc310-mnc410 \ No newline at end of file
diff --git a/res/values-mcc313-mnc100 b/res/values-mcc313-mnc100
new file mode 120000
index 000000000..78e65039e
--- /dev/null
+++ b/res/values-mcc313-mnc100
@@ -0,0 +1 @@
+values-mcc310-mnc410 \ No newline at end of file
diff --git a/res/values-mcc313-mnc110 b/res/values-mcc313-mnc110
index 2d21ee5ba..78e65039e 120000
--- a/res/values-mcc313-mnc110
+++ b/res/values-mcc313-mnc110
@@ -1 +1 @@
-values-mcc313-mnc100 \ No newline at end of file
+values-mcc310-mnc410 \ No newline at end of file
diff --git a/res/values-mcc313-mnc120 b/res/values-mcc313-mnc120
index 2d21ee5ba..78e65039e 120000
--- a/res/values-mcc313-mnc120
+++ b/res/values-mcc313-mnc120
@@ -1 +1 @@
-values-mcc313-mnc100 \ No newline at end of file
+values-mcc310-mnc410 \ No newline at end of file
diff --git a/res/values-mcc313-mnc130 b/res/values-mcc313-mnc130
index 2d21ee5ba..78e65039e 120000
--- a/res/values-mcc313-mnc130
+++ b/res/values-mcc313-mnc130
@@ -1 +1 @@
-values-mcc313-mnc100 \ No newline at end of file
+values-mcc310-mnc410 \ No newline at end of file
diff --git a/res/values-mcc313-mnc140 b/res/values-mcc313-mnc140
index 2d21ee5ba..78e65039e 120000
--- a/res/values-mcc313-mnc140
+++ b/res/values-mcc313-mnc140
@@ -1 +1 @@
-values-mcc313-mnc100 \ No newline at end of file
+values-mcc310-mnc410 \ No newline at end of file
diff --git a/res/values-mcc314 b/res/values-mcc314
new file mode 120000
index 000000000..e454f498c
--- /dev/null
+++ b/res/values-mcc314
@@ -0,0 +1 @@
+values-mcc310 \ No newline at end of file
diff --git a/res/values-mcc314/config.xml b/res/values-mcc314/config.xml
deleted file mode 120000
index 0dc8a9de1..000000000
--- a/res/values-mcc314/config.xml
+++ /dev/null
@@ -1 +0,0 @@
-../values-mcc310/config.xml \ No newline at end of file
diff --git a/res/values-mcc315 b/res/values-mcc315
new file mode 120000
index 000000000..e454f498c
--- /dev/null
+++ b/res/values-mcc315
@@ -0,0 +1 @@
+values-mcc310 \ No newline at end of file
diff --git a/res/values-mcc315/config.xml b/res/values-mcc315/config.xml
deleted file mode 120000
index 0dc8a9de1..000000000
--- a/res/values-mcc315/config.xml
+++ /dev/null
@@ -1 +0,0 @@
-../values-mcc310/config.xml \ No newline at end of file
diff --git a/res/values-mcc316 b/res/values-mcc316
new file mode 120000
index 000000000..e454f498c
--- /dev/null
+++ b/res/values-mcc316
@@ -0,0 +1 @@
+values-mcc310 \ No newline at end of file
diff --git a/res/values-mcc316/config.xml b/res/values-mcc316/config.xml
deleted file mode 120000
index 0dc8a9de1..000000000
--- a/res/values-mcc316/config.xml
+++ /dev/null
@@ -1 +0,0 @@
-../values-mcc310/config.xml \ No newline at end of file
diff --git a/res/values-mcc424/strings.xml b/res/values-mcc424/strings.xml
index 3d5065938..78e27b456 100644
--- a/res/values-mcc424/strings.xml
+++ b/res/values-mcc424/strings.xml
@@ -24,4 +24,5 @@
<string name="cmas_severe_alert" translatable="false">@string/emergency_alert</string>
<string name="cmas_amber_alert">Warning alert</string>
<string name="cmas_required_monthly_test">Test alert</string>
-</resources> \ No newline at end of file
+ <string name="cmas_exercise_alert">Exercise</string>
+</resources>
diff --git a/res/values-mcc530/config.xml b/res/values-mcc530/config.xml
index 38c5bd810..dce586462 100644
--- a/res/values-mcc530/config.xml
+++ b/res/values-mcc530/config.xml
@@ -17,4 +17,8 @@
<resources>
<bool name="show_date_time_title">true</bool>
<bool name="use_full_volume">true</bool>
+ <!-- always bypass user settings and play presidential alert at full volumes -->
+ <bool name="full_volume_presidential_alert">true</bool>
+ <!-- Disable Amber alert-->
+ <string-array name="cmas_amber_alerts_channels_range_strings" translatable="false"></string-array>
</resources>
diff --git a/res/values-mcc530/strings.xml b/res/values-mcc530/strings.xml
index 1fcf240ef..a2a292fbe 100644
--- a/res/values-mcc530/strings.xml
+++ b/res/values-mcc530/strings.xml
@@ -18,4 +18,12 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Special New Zealand requirement for the presidential alert title -->
<string name="cmas_presidential_level_alert" translatable="false">@string/emergency_alert</string>
+ <!-- CMAS dialog title for extreme alert. [CHAR LIMIT=50] -->
+ <string name="cmas_extreme_alert">@string/emergency_alert</string>
+ <!-- CMAS dialog title for extreme alert with extreme severity, immediate urgency, and observed certainty. [CHAR LIMIT=50] -->
+ <string name="cmas_extreme_immediate_observed_alert">@string/emergency_alert</string>
+ <!-- CMAS dialog title for extreme alert with extreme severity, immediate urgency, and likely certainty. [CHAR LIMIT=50] -->
+ <string name="cmas_extreme_immediate_likely_alert">@string/emergency_alert</string>
+ <!-- CMAS dialog title for severe alert. [CHAR LIMIT=50] -->
+ <string name="cmas_severe_alert">@string/emergency_alert</string>
</resources>
diff --git a/res/values-mcc716/config.xml b/res/values-mcc716/config.xml
index ef47e57f2..5752117d3 100644
--- a/res/values-mcc716/config.xml
+++ b/res/values-mcc716/config.xml
@@ -63,4 +63,7 @@
<!-- Disable links for Peru -->
<integer-array name="message_classes_to_linkify"></integer-array>
+
+ <!-- Hide public safety settings per Peru users -->
+ <bool name="show_public_safety_settings">false</bool>
</resources>
diff --git a/res/values/config.xml b/res/values/config.xml
index 2b1f4e1b9..a160b7812 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -15,7 +15,7 @@
-->
<resources>
- <!-- Whether to always sound CBS alerts at full volume -->
+ <!-- Whether to bypass Do-Not-Disturb/silent mode and play emergency alert tone at full volume. -->
<bool name="use_full_volume">false</bool>
<!-- Whether to enable CMAS settings (United States) -->
<bool name="show_cmas_settings">true</bool>
@@ -33,7 +33,12 @@
<bool name="show_cmas_messages_in_priority_order">false</bool>
<!-- Whether to ignore any alert message in ECBM(Emergency Callback Mode) -->
<bool name="ignore_messages_in_ecbm">false</bool>
-
+ <!-- Show checkbox for Presidential alerts in settings -->
+ <bool name="show_presidential_alerts_in_settings">false</bool>
+ <!-- Whether to display public safety alert settings, some countries/carriers want to enable it by default and not allow users to disable -->
+ <bool name="show_public_safety_settings">true</bool>
+ <!-- Whether to bypass Do-Not-Disturb/silent mode and play emergency alert tone at full volume for presidential alert -->
+ <bool name="full_volume_presidential_alert">false</bool>
<!-- 4370, 4383 -->
<string-array name="cmas_presidential_alerts_channels_range_strings" translatable="false">
<item>0x1112:rat=gsm, emergency=true</item>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 55379cf87..feae57f34 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -278,4 +278,10 @@
<!-- Label to open emergencyNotifications dialog [CHAR LIMIT=40] -->
<string name="emergency_alert_settings_title_watches">Wireless emergency alerts</string>
+
+ <!-- Show checkbox for Presidential alerts in settings -->
+ <!-- Preference title for enable presidential threat alerts checkbox. [CHAR LIMIT=40] -->
+ <string name="enable_cmas_presidential_alerts_title">Presidential alerts</string>
+ <!-- Preference summary for enable presidential threat alerts checkbox. [CHAR LIMIT=100] -->
+ <string name="enable_cmas_presidential_alerts_summary">National warning messages issued by the President. Can\'t be turned off.</string>
</resources>
diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml
index e72f44625..8b9698da2 100644
--- a/res/xml/preferences.xml
+++ b/res/xml/preferences.xml
@@ -33,6 +33,13 @@
android:summary="@string/enable_emergency_alerts_message_summary"
android:title="@string/enable_emergency_alerts_message_title" />
+ <!-- Show checkbox for Presidential alerts in settings -->
+ <SwitchPreference android:defaultValue="true"
+ android:enabled="false"
+ android:key="enable_cmas_presidential_alerts"
+ android:summary="@string/enable_cmas_presidential_alerts_summary"
+ android:title="@string/enable_cmas_presidential_alerts_title"/>
+
<!-- Enable CMAS AMBER alerts -->
<SwitchPreference android:defaultValue="true"
android:key="enable_cmas_amber_alerts"
diff --git a/src/com/android/cellbroadcastreceiver/CbGeoUtils.java b/src/com/android/cellbroadcastreceiver/CbGeoUtils.java
deleted file mode 100644
index 9a8697ad4..000000000
--- a/src/com/android/cellbroadcastreceiver/CbGeoUtils.java
+++ /dev/null
@@ -1,316 +0,0 @@
-/*
- * Copyright (C) 2019 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.annotation.NonNull;
-import android.telephony.Rlog;
-import android.text.TextUtils;
-
-import com.android.internal.telephony.CbGeoUtils.Geometry;
-import com.android.internal.telephony.CbGeoUtils.LatLng;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * This utils class is specifically used for geo-targeting of CellBroadcast messages.
- * The coordinates used by this utils class are latitude and longitude, but some algorithms in this
- * class only use them as coordinates on plane, so the calculation will be inaccurate. So don't use
- * this class for anything other then geo-targeting of cellbroadcast messages.
- */
-public class CbGeoUtils {
- /**
- * Tolerance for determining if the value is 0. If the absolute value of a value is less than
- * this tolerance, it will be treated as 0.
- */
- public static final double EPS = 1e-7;
-
- private static final String TAG = "CbGeoUtils";
-
- /** The TLV tags of WAC, defined in ATIS-0700041 5.2.3 WAC tag coding. */
- public static final int GEO_FENCING_MAXIMUM_WAIT_TIME = 0x01;
- public static final int GEOMETRY_TYPE_POLYGON = 0x02;
- public static final int GEOMETRY_TYPE_CIRCLE = 0x03;
-
- /** The identifier of geometry in the encoded string. */
- private static final String CIRCLE_SYMBOL = "circle";
- private static final String POLYGON_SYMBOL = "polygon";
-
- /**
- * The class represents a simple polygon with at least 3 points.
- */
- public static class Polygon implements com.android.internal.telephony.CbGeoUtils.Geometry {
- /**
- * In order to reduce the loss of precision in floating point calculations, all vertices
- * of the polygon are scaled. Set the value of scale to 1000 can take into account the
- * actual distance accuracy of 1 meter if the EPS is 1e-7 during the calculation.
- */
- private static final double SCALE = 1000.0;
-
- private final List<LatLng> mVertices;
- private final List<Point> mScaledVertices;
- private final LatLng mOrigin;
-
- /**
- * Constructs a simple polygon from the given vertices. The adjacent two vertices are
- * connected to form an edge of the polygon. The polygon has at least 3 vertices, and the
- * last vertices and the first vertices must be adjacent.
- *
- * The longitude difference in the vertices should be less than 180 degree.
- */
- public Polygon(@NonNull List<LatLng> vertices) {
- mVertices = vertices;
-
- // Find the point with smallest longitude as the mOrigin point.
- int idx = 0;
- for (int i = 1; i < vertices.size(); i++) {
- if (vertices.get(i).lng < vertices.get(idx).lng) {
- idx = i;
- }
- }
- mOrigin = vertices.get(idx);
-
- mScaledVertices = vertices.stream()
- .map(latLng -> convertAndScaleLatLng(latLng))
- .collect(Collectors.toList());
- }
-
- public List<LatLng> getVertices() {
- return mVertices;
- }
-
- /**
- * Check if the given point {@code p} is inside the polygon. This method counts the number
- * of times the polygon winds around the point P, A.K.A "winding number". The point is
- * outside only when this "winding number" is 0.
- *
- * If a point is on the edge of the polygon, it is also considered to be inside the polygon.
- */
- @Override
- public boolean contains(LatLng latLng) {
- Point p = convertAndScaleLatLng(latLng);
-
- int n = mScaledVertices.size();
- int windingNumber = 0;
- for (int i = 0; i < n; i++) {
- Point a = mScaledVertices.get(i);
- Point b = mScaledVertices.get((i + 1) % n);
-
- // CCW is counterclockwise
- // CCW = ab x ap
- // CCW > 0 -> ap is on the left side of ab
- // CCW == 0 -> ap is on the same line of ab
- // CCW < 0 -> ap is on the right side of ab
- int ccw = sign(crossProduct(b.subtract(a), p.subtract(a)));
-
- if (ccw == 0) {
- if (Math.min(a.x, b.x) <= p.x && p.x <= Math.max(a.x, b.x)
- && Math.min(a.y, b.y) <= p.y && p.y <= Math.max(a.y, b.y)) {
- return true;
- }
- } else {
- if (sign(a.y - p.y) <= 0) {
- // upward crossing
- if (ccw > 0 && sign(b.y - p.y) > 0) {
- ++windingNumber;
- }
- } else {
- // downward crossing
- if (ccw < 0 && sign(b.y - p.y) <= 0) {
- --windingNumber;
- }
- }
- }
- }
- return windingNumber != 0;
- }
-
- /**
- * Move the given point {@code latLng} to the coordinate system with {@code mOrigin} as the
- * origin and scale it. {@code mOrigin} is selected from the vertices of a polygon, it has
- * the smallest longitude value among all of the polygon vertices.
- *
- * @param latLng the point need to be converted and scaled.
- * @Return a {@link Point} object.
- */
- private Point convertAndScaleLatLng(LatLng latLng) {
- double x = latLng.lat - mOrigin.lat;
- double y = latLng.lng - mOrigin.lng;
-
- // If the point is in different hemispheres(western/eastern) than the mOrigin, and the
- // edge between them cross the 180th meridian, then its relative coordinates will be
- // extended.
- // For example, suppose the longitude of the mOrigin is -178, and the longitude of the
- // point to be converted is 175, then the longitude after the conversion is -8.
- // calculation: (-178 - 8) - (-178).
- if (sign(mOrigin.lng) != 0 && sign(mOrigin.lng) != sign(latLng.lng)) {
- double distCross0thMeridian = Math.abs(mOrigin.lng) + Math.abs(latLng.lng);
- if (sign(distCross0thMeridian * 2 - 360) > 0) {
- y = sign(mOrigin.lng) * (360 - distCross0thMeridian);
- }
- }
- return new Point(x * SCALE, y * SCALE);
- }
-
- private static double crossProduct(Point a, Point b) {
- return a.x * b.y - a.y * b.x;
- }
-
- static final class Point {
- public final double x;
- public final double y;
-
- Point(double x, double y) {
- this.x = x;
- this.y = y;
- }
-
- public Point subtract(Point p) {
- return new Point(x - p.x, y - p.y);
- }
- }
- }
-
- /** The class represents a circle. */
- public static class Circle implements Geometry {
- private final LatLng mCenter;
- private final double mRadiusMeter;
-
- public Circle(LatLng center, double radiusMeter) {
- this.mCenter = center;
- this.mRadiusMeter = radiusMeter;
- }
-
- public LatLng getCenter() {
- return mCenter;
- }
-
- public double getRadius() {
- return mRadiusMeter;
- }
-
- @Override
- public boolean contains(LatLng p) {
- return mCenter.distance(p) <= mRadiusMeter;
- }
- }
-
- /**
- * Parse the geometries from the encoded string {@code str}. The string must follow the
- * geometry encoding specified by {@link android.provider.Telephony.CellBroadcasts#GEOMETRIES}.
- */
- @NonNull
- public static List<Geometry> parseGeometriesFromString(@NonNull String str) {
- List<Geometry> geometries = new ArrayList<>();
- for (String geometryStr : str.split("\\s*;\\s*")) {
- String[] geoParameters = geometryStr.split("\\s*\\|\\s*");
- switch (geoParameters[0]) {
- case CIRCLE_SYMBOL:
- geometries.add(new Circle(parseLatLngFromString(geoParameters[1]),
- Double.parseDouble(geoParameters[2])));
- break;
- case POLYGON_SYMBOL:
- List<LatLng> vertices = new ArrayList<>(geoParameters.length - 1);
- for (int i = 1; i < geoParameters.length; i++) {
- vertices.add(parseLatLngFromString(geoParameters[i]));
- }
- geometries.add(new Polygon(vertices));
- break;
- default:
- Rlog.e(TAG, "Invalid geometry format " + geometryStr);
- }
- }
- return geometries;
- }
-
- /**
- * Encode a list of geometry objects to string. The encoding format is specified by
- * {@link android.provider.Telephony.CellBroadcasts#GEOMETRIES}.
- *
- * @param geometries the list of geometry objects need to be encoded.
- * @return the encoded string.
- */
- @NonNull
- public static String encodeGeometriesToString(@NonNull List<Geometry> geometries) {
- return geometries.stream()
- .map(geometry -> encodeGeometryToString(geometry))
- .filter(encodedStr -> !TextUtils.isEmpty(encodedStr))
- .collect(Collectors.joining(";"));
- }
-
-
- /**
- * Encode the geometry object to string. The encoding format is specified by
- * {@link android.provider.Telephony.CellBroadcasts#GEOMETRIES}.
- * @param geometry the geometry object need to be encoded.
- * @return the encoded string.
- */
- @NonNull
- private static String encodeGeometryToString(@NonNull Geometry geometry) {
- StringBuilder sb = new StringBuilder();
- if (geometry instanceof Polygon) {
- sb.append(POLYGON_SYMBOL);
- for (LatLng latLng : ((Polygon) geometry).getVertices()) {
- sb.append("|");
- sb.append(latLng.lat);
- sb.append(",");
- sb.append(latLng.lng);
- }
- } else if (geometry instanceof Circle) {
- sb.append(CIRCLE_SYMBOL);
- Circle circle = (Circle) geometry;
-
- // Center
- sb.append("|");
- sb.append(circle.getCenter().lat);
- sb.append(",");
- sb.append(circle.getCenter().lng);
-
- // Radius
- sb.append("|");
- sb.append(circle.getRadius());
- } else {
- Rlog.e(TAG, "Unsupported geometry object " + geometry);
- return null;
- }
- return sb.toString();
- }
-
- /**
- * Parse {@link LatLng} from {@link String}. Latitude and longitude are separated by ",".
- * Example: "13.56,-55.447".
- *
- * @param str encoded lat/lng string.
- * @Return {@link LatLng} object.
- */
- @NonNull
- public static LatLng parseLatLngFromString(@NonNull String str) {
- String[] latLng = str.split("\\s*,\\s*");
- return new LatLng(Double.parseDouble(latLng[0]), Double.parseDouble(latLng[1]));
- }
-
- /**
- * @Return the sign of the given value {@code value} with the specified tolerance. Return 1
- * means the sign is positive, -1 means negative, 0 means the value will be treated as 0.
- */
- public static int sign(double value) {
- if (value > EPS) return 1;
- if (value < -EPS) return -1;
- return 0;
- }
-}
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertAudio.java b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertAudio.java
index 05d720748..78ffb20ea 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertAudio.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertAudio.java
@@ -16,6 +16,8 @@
package com.android.cellbroadcastreceiver;
+import static android.telephony.PhoneStateListener.LISTEN_NONE;
+
import static com.android.cellbroadcastreceiver.CellBroadcastReceiver.DBG;
import android.app.Service;
@@ -39,12 +41,10 @@ import android.preference.PreferenceManager;
import android.provider.Settings;
import android.speech.tts.TextToSpeech;
import android.telephony.PhoneStateListener;
-import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
-import android.util.SparseArray;
import com.android.cellbroadcastreceiver.CellBroadcastAlertService.AlertType;
@@ -75,7 +75,15 @@ public class CellBroadcastAlertAudio extends Service implements TextToSpeech.OnI
/** Extra for alert vibration pattern (unless master volume is silent). */
public static final String ALERT_AUDIO_VIBRATION_PATTERN_EXTRA =
- "com.android.cellbroadcastreceiver.ALERT_VIBRATION_PATTERN";
+ "com.android.cellbroadcastreceiver.ALERT_AUDIO_VIBRATION_PATTERN";
+
+ /** Extra for always sound alerts at full volume. */
+ public static final String ALERT_AUDIO_FULL_VOLUME_EXTRA =
+ "com.android.cellbroadcastreceiver.ALERT_FULL_VOLUME_EXTRA";
+
+ /** Extra for alert subscription index */
+ public static final String ALERT_AUDIO_SUB_INDEX =
+ "com.android.cellbroadcastreceiver.ALERT_AUDIO_SUB_INDEX";
private static final String TTS_UTTERANCE_ID = "com.android.cellbroadcastreceiver.UTTERANCE_ID";
@@ -92,8 +100,10 @@ public class CellBroadcastAlertAudio extends Service implements TextToSpeech.OnI
private TextToSpeech mTts;
private boolean mTtsEngineReady;
+ private AlertType mAlertType;
private String mMessageBody;
private String mMessageLanguage;
+ private int mSubId;
private boolean mTtsLanguageSupported;
private boolean mEnableVibrate;
private boolean mEnableAudio;
@@ -106,9 +116,6 @@ public class CellBroadcastAlertAudio extends Service implements TextToSpeech.OnI
private MediaPlayer mMediaPlayer;
private AudioManager mAudioManager;
private TelephonyManager mTelephonyManager;
- private SubscriptionManager mSubscriptionManager;
- private int mPhoneCount;
- private SparseArray<PhoneStateListener> mPhoneStateListeners = new SparseArray<>();
private int mInitialCallState;
// Internal messages
@@ -143,7 +150,7 @@ public class CellBroadcastAlertAudio extends Service implements TextToSpeech.OnI
if (mMessageBody != null && mTtsEngineReady && mTtsLanguageSupported) {
if (DBG) log("Speaking broadcast text: " + mMessageBody);
- mTts.setAudioAttributes(getAlertAudioAttributes());
+ mTts.setAudioAttributes(getAlertAudioAttributes(mAlertType));
res = mTts.speak(mMessageBody, 2, null, TTS_UTTERANCE_ID);
mState = STATE_SPEAKING;
}
@@ -156,6 +163,17 @@ public class CellBroadcastAlertAudio extends Service implements TextToSpeech.OnI
default:
loge("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();
}
}
};
@@ -220,34 +238,8 @@ public class CellBroadcastAlertAudio extends Service implements TextToSpeech.OnI
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);
- mSubscriptionManager =
- (SubscriptionManager) getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
-
- mPhoneCount = mTelephonyManager.getMaxPhoneCount();
- for (int i = 0; i < mPhoneCount; i++) {
- final SubscriptionInfo sir =
- mSubscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(i);
- if (sir == null) continue;
- final int subId = sir.getSubscriptionId();
- PhoneStateListener listener =
- 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();
- }
- }
- };
-
- mPhoneStateListeners.put(subId, listener);
- mTelephonyManager
- .createForSubscriptionId(subId)
- .listen(listener, PhoneStateListener.LISTEN_CALL_STATE);
- }
+ mTelephonyManager = ((TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE));
+ mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
}
@Override
@@ -255,17 +247,7 @@ public class CellBroadcastAlertAudio extends Service implements TextToSpeech.OnI
// stop audio, vibration and TTS
stop();
// Stop listening for incoming calls.
- for (int i = 0; i < mPhoneCount; i++) {
- final SubscriptionInfo sir =
- mSubscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(i);
- if (sir == null) continue;
- final int subId = sir.getSubscriptionId();
- PhoneStateListener listener = mPhoneStateListeners.get(subId);
- if (listener == null) continue;
- mTelephonyManager
- .createForSubscriptionId(subId)
- .listen(listener, PhoneStateListener.LISTEN_NONE);
- }
+ mTelephonyManager.listen(mPhoneStateListener, LISTEN_NONE);
// shutdown TTS engine
if (mTts != null) {
try {
@@ -299,16 +281,22 @@ public class CellBroadcastAlertAudio extends Service implements TextToSpeech.OnI
// Get text to speak (if enabled by user)
mMessageBody = intent.getStringExtra(ALERT_AUDIO_MESSAGE_BODY);
mMessageLanguage = intent.getStringExtra(ALERT_AUDIO_MESSAGE_LANGUAGE);
+ mSubId = intent.getIntExtra(ALERT_AUDIO_SUB_INDEX,
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
- // Get config of whether to always sound CBS alerts at full volume.
- mUseFullVolume = prefs.getBoolean(CellBroadcastSettings.KEY_USE_FULL_VOLUME, false);
-
+ // retrieve whether always sound CBS alerts at full volume.
+ mUseFullVolume = intent.getBooleanExtra(ALERT_AUDIO_FULL_VOLUME_EXTRA, false);
// retrieve the vibrate settings from cellbroadcast receiver settings.
mEnableVibrate = prefs.getBoolean(CellBroadcastSettings.KEY_ENABLE_ALERT_VIBRATE, true);
- // retrieve the vibration patterns
+ // retrieve the vibration patterns.
mVibrationPattern = intent.getIntArrayExtra(ALERT_AUDIO_VIBRATION_PATTERN_EXTRA);
+ // retrieve the alert type
+ mAlertType = AlertType.DEFAULT;
+ if (intent.getSerializableExtra(ALERT_AUDIO_TONE_TYPE) != null) {
+ mAlertType = (AlertType) intent.getSerializableExtra(ALERT_AUDIO_TONE_TYPE);
+ }
switch (mAudioManager.getRingerMode()) {
case AudioManager.RINGER_MODE_SILENT:
@@ -342,11 +330,7 @@ public class CellBroadcastAlertAudio extends Service implements TextToSpeech.OnI
}
if (mEnableAudio || mEnableVibrate) {
- AlertType alertType = AlertType.DEFAULT;
- if (intent.getSerializableExtra(ALERT_AUDIO_TONE_TYPE) != null) {
- alertType = (AlertType) intent.getSerializableExtra(ALERT_AUDIO_TONE_TYPE);
- }
- playAlertTone(alertType, mVibrationPattern);
+ playAlertTone(mAlertType, mVibrationPattern);
} else {
stopSelf();
return START_NOT_STICKY;
@@ -373,10 +357,9 @@ public class CellBroadcastAlertAudio extends Service implements TextToSpeech.OnI
stop();
log("playAlertTone: alertType=" + alertType + ", mEnableVibrate=" + mEnableVibrate
- + ", mEnableAudio=" + mEnableAudio + ", mUseFullVolume=" + mUseFullVolume);
- Resources res =
- CellBroadcastSettings.getResourcesForDefaultSmsSubscriptionId(
- getApplicationContext());
+ + ", mEnableAudio=" + mEnableAudio + ", mUseFullVolume=" + mUseFullVolume
+ + ", mSubId=" + mSubId);
+ Resources res = CellBroadcastSettings.getResources(getApplicationContext(), mSubId);
// Vibration duration in milliseconds
long vibrateDuration = 0;
@@ -395,7 +378,8 @@ public class CellBroadcastAlertAudio extends Service implements TextToSpeech.OnI
}
AudioAttributes.Builder attrBuilder = new AudioAttributes.Builder();
- attrBuilder.setUsage(AudioAttributes.USAGE_ALARM);
+ attrBuilder.setUsage(alertType == AlertType.INFO
+ ? AudioAttributes.USAGE_NOTIFICATION : AudioAttributes.USAGE_ALARM);
if (mUseFullVolume) {
// Set the flags to bypass DnD mode if the user enables use full volume option.
attrBuilder.setFlags(AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY
@@ -471,8 +455,8 @@ public class CellBroadcastAlertAudio extends Service implements TextToSpeech.OnI
// Request audio focus (though we're going to play even if we don't get it)
mAudioManager.requestAudioFocus(null, AudioManager.STREAM_ALARM,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
- mMediaPlayer.setAudioAttributes(getAlertAudioAttributes());
- setAlertVolume();
+ mMediaPlayer.setAudioAttributes(getAlertAudioAttributes(mAlertType));
+ setAlertVolume(mAlertType);
// If we are using the custom alert duration, set looping to true so we can repeat
// the alert. The tone playing will stop when ALERT_SOUND_FINISHED arrives.
@@ -518,7 +502,7 @@ public class CellBroadcastAlertAudio extends Service implements TextToSpeech.OnI
mHandler.removeMessages(ALERT_SOUND_FINISHED);
mHandler.removeMessages(ALERT_PAUSE_FINISHED);
- resetAlarmStreamVolume();
+ resetAlarmStreamVolume(mAlertType);
if (mState == STATE_ALERTING) {
// Stop audio playing
@@ -549,11 +533,12 @@ public class CellBroadcastAlertAudio extends Service implements TextToSpeech.OnI
/**
* Get audio attribute for the alarm.
*/
- private AudioAttributes getAlertAudioAttributes() {
+ private AudioAttributes getAlertAudioAttributes(AlertType alertType) {
AudioAttributes.Builder builder = new AudioAttributes.Builder();
builder.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION);
- builder.setUsage(AudioAttributes.USAGE_ALARM);
+ builder.setUsage((alertType == AlertType.INFO
+ ? AudioAttributes.USAGE_NOTIFICATION : AudioAttributes.USAGE_ALARM));
if (mUseFullVolume) {
// Set FLAG_BYPASS_INTERRUPTION_POLICY and FLAG_BYPASS_MUTE so that it enables
// audio in any DnD mode, even in total silence DnD mode (requires MODIFY_PHONE_STATE).
@@ -567,7 +552,7 @@ public class CellBroadcastAlertAudio extends Service implements TextToSpeech.OnI
/**
* Set volume for alerts.
*/
- private void setAlertVolume() {
+ private void setAlertVolume(AlertType alertType) {
if (mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE
|| isOnEarphone()) {
// If we are in a call, play the alert
@@ -578,7 +563,7 @@ public class CellBroadcastAlertAudio extends Service implements TextToSpeech.OnI
// If use_full_volume is configured,
// we overwrite volume setting of STREAM_ALARM to full, play at
// max possible volume, and reset it after it's finished.
- setAlarmStreamVolumeToFull();
+ setAlarmStreamVolumeToFull(alertType);
}
}
@@ -601,22 +586,25 @@ public class CellBroadcastAlertAudio extends Service implements TextToSpeech.OnI
/**
* Set volume of STREAM_ALARM to full.
*/
- private void setAlarmStreamVolumeToFull() {
+ private void setAlarmStreamVolumeToFull(AlertType alertType) {
log("setting alarm volume to full for cell broadcast alerts.");
- mUserSetAlarmVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_ALARM);
+ int streamType = (alertType == AlertType.INFO)
+ ? AudioManager.STREAM_NOTIFICATION : AudioManager.STREAM_ALARM;
+ mUserSetAlarmVolume = mAudioManager.getStreamVolume(streamType);
mResetAlarmVolumeNeeded = true;
- mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM,
- mAudioManager.getStreamMaxVolume(AudioManager.STREAM_ALARM),
- 0);
+ mAudioManager.setStreamVolume(streamType,
+ mAudioManager.getStreamMaxVolume(streamType), 0);
}
/**
* Reset volume of STREAM_ALARM, if needed.
*/
- private void resetAlarmStreamVolume() {
+ private void resetAlarmStreamVolume(AlertType alertType) {
if (mResetAlarmVolumeNeeded) {
log("resetting alarm volume to back to " + mUserSetAlarmVolume);
- mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, mUserSetAlarmVolume, 0);
+ mAudioManager.setStreamVolume(alertType == AlertType.INFO
+ ? AudioManager.STREAM_NOTIFICATION : AudioManager.STREAM_ALARM,
+ mUserSetAlarmVolume, 0);
mResetAlarmVolumeNeeded = false;
}
}
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertDialog.java b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertDialog.java
index 02c8831ae..c07416646 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertDialog.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertDialog.java
@@ -29,11 +29,10 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.PowerManager;
-import android.os.UserManager;
import android.preference.PreferenceManager;
import android.provider.Telephony;
-import android.telephony.CellBroadcastMessage;
import android.telephony.SmsCbCmasInfo;
+import android.telephony.SubscriptionManager;
import android.text.util.Linkify;
import android.util.Log;
import android.view.KeyEvent;
@@ -109,8 +108,8 @@ public class CellBroadcastAlertDialog extends Activity {
AnimationHandler() {}
/** Start the warning icon animation. */
- void startIconAnimation() {
- if (!initDrawableAndImageView()) {
+ void startIconAnimation(int subId) {
+ if (!initDrawableAndImageView(subId)) {
return; // init failure
}
mWarningIconVisible = true;
@@ -152,13 +151,16 @@ public class CellBroadcastAlertDialog extends Activity {
/**
* Initialize the Drawable and ImageView fields.
+ *
+ * @param subId Subscription index
+ *
* @return true if successful; false if any field failed to initialize
*/
- private boolean initDrawableAndImageView() {
+ private boolean initDrawableAndImageView(int subId) {
if (mWarningIcon == null) {
try {
- mWarningIcon = CellBroadcastSettings.getResourcesForDefaultSmsSubscriptionId(
- getApplicationContext()).getDrawable(R.drawable.ic_warning_googred);
+ mWarningIcon = CellBroadcastSettings.getResources(getApplicationContext(),
+ subId).getDrawable(R.drawable.ic_warning_googred);
} catch (Resources.NotFoundException e) {
Log.e(TAG, "warning icon resource not found", e);
return false;
@@ -278,10 +280,14 @@ public class CellBroadcastAlertDialog extends Activity {
// For emergency alerts, keep screen on so the user can read it
CellBroadcastMessage message = getLatestMessage();
- if (message != null && CellBroadcastChannelManager.isEmergencyMessage(
- this, message)) {
- Log.d(TAG, "onCreate setting screen on timer for emergency alert");
- mScreenOffHandler.startScreenOnTimer();
+ if (message != null) {
+ CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager(
+ this, message.getSubId(this));
+ if (channelManager.isEmergencyMessage(message)) {
+ Log.d(TAG, "onCreate setting screen on timer for emergency alert for sub "
+ + message.getSubId(this));
+ mScreenOffHandler.startScreenOnTimer();
+ }
}
updateAlertText(message);
@@ -295,8 +301,13 @@ public class CellBroadcastAlertDialog extends Activity {
protected void onResume() {
super.onResume();
CellBroadcastMessage message = getLatestMessage();
- if (message != null && CellBroadcastChannelManager.isEmergencyMessage(this, message)) {
- mAnimationHandler.startIconAnimation();
+ if (message != null) {
+ int subId = message.getSubId(this);
+ CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager(this,
+ subId);
+ if (channelManager.isEmergencyMessage(message)) {
+ mAnimationHandler.startIconAnimation(subId);
+ }
}
}
@@ -365,7 +376,7 @@ public class CellBroadcastAlertDialog extends Activity {
String title = getText(titleId).toString();
TextView titleTextView = findViewById(R.id.alertTitle);
- if (CellBroadcastSettings.getResourcesForDefaultSmsSubscriptionId(context)
+ if (CellBroadcastSettings.getResources(context, message.getSubId(context))
.getBoolean(R.bool.show_date_time_title)) {
titleTextView.setSingleLine(false);
title += "\n" + message.getDateString(context);
@@ -425,8 +436,8 @@ public class CellBroadcastAlertDialog extends Activity {
mMessageList = newMessageList;
} else {
mMessageList.addAll(newMessageList);
- if (CellBroadcastSettings.getResourcesForDefaultSmsSubscriptionId(
- getApplicationContext())
+ if (CellBroadcastSettings.getResources(getApplicationContext(),
+ SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)
.getBoolean(R.bool.show_cmas_messages_in_priority_order)) {
// Sort message list to show messages in a different order than received by
// prioritizing them. Presidential Alert only has top priority.
@@ -520,9 +531,11 @@ public class CellBroadcastAlertDialog extends Activity {
CellBroadcastMessage nextMessage = getLatestMessage();
if (nextMessage != null) {
updateAlertText(nextMessage);
- if (CellBroadcastChannelManager.isEmergencyMessage(
- this, nextMessage)) {
- mAnimationHandler.startIconAnimation();
+ int subId = nextMessage.getSubId(getApplicationContext());
+ CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager(
+ getApplicationContext(), subId);
+ if (channelManager.isEmergencyMessage(nextMessage)) {
+ mAnimationHandler.startIconAnimation(subId);
} else {
mAnimationHandler.stopIconAnimation();
}
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertService.java b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertService.java
index 99bdeadec..e77d3e66b 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertService.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertService.java
@@ -39,7 +39,7 @@ import android.os.UserHandle;
import android.preference.PreferenceManager;
import android.provider.Telephony;
import android.telephony.CarrierConfigManager;
-import android.telephony.CellBroadcastMessage;
+import android.telephony.SmsCbCmasInfo;
import android.telephony.SmsCbEtwsInfo;
import android.telephony.SmsCbLocation;
import android.telephony.SmsCbMessage;
@@ -104,6 +104,8 @@ public class CellBroadcastAlertService extends Service {
private static final String MESSAGE_FILTER_PROPERTY_KEY =
"persist.cellbroadcast.message_filter";
+ private Context mContext;
+
/**
* Alert type
*/
@@ -180,6 +182,7 @@ public class CellBroadcastAlertService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
+ mContext = getApplicationContext();
String action = intent.getAction();
Log.d(TAG, "onStartCommand: " + action);
if (Telephony.Sms.Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION.equals(action) ||
@@ -215,8 +218,8 @@ public class CellBroadcastAlertService extends Service {
* values indicate the duplicate will always be ignored. The default value would be 24 hours.
*/
private long getDuplicateExpirationTime(int subId) {
- CarrierConfigManager configManager = (CarrierConfigManager)
- getApplicationContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
+ CarrierConfigManager configManager = (CarrierConfigManager) mContext.getSystemService(
+ Context.CARRIER_CONFIG_SERVICE);
Log.d(TAG, "manager = " + configManager);
if (configManager == null) {
Log.e(TAG, "carrier config is not available.");
@@ -241,14 +244,10 @@ public class CellBroadcastAlertService extends Service {
* @return True if the message should be displayed to the user
*/
private boolean shouldDisplayMessage(CellBroadcastMessage cbm) {
- TelephonyManager tm =
- ((TelephonyManager)
- getApplicationContext().getSystemService(Context.TELEPHONY_SERVICE))
- .createForSubscriptionId(cbm.getSubId());
- if (tm.getEmergencyCallbackMode()
- && CellBroadcastSettings.getResourcesForDefaultSmsSubscriptionId(
- getApplicationContext())
- .getBoolean(R.bool.ignore_messages_in_ecbm)) {
+ TelephonyManager tm = ((TelephonyManager) mContext.getSystemService(
+ Context.TELEPHONY_SERVICE)).createForSubscriptionId(cbm.getSubId(mContext));
+ if (tm.getEmergencyCallbackMode() && CellBroadcastSettings.getResources(
+ mContext, cbm.getSubId(mContext)).getBoolean(R.bool.ignore_messages_in_ecbm)) {
// Ignore the message in ECBM.
// It is for LTE only mode. For 1xRTT, incoming pages should be ignored in the modem.
Log.d(TAG, "ignoring alert of type " + cbm.getServiceCategory() + " in ECBM");
@@ -269,8 +268,10 @@ public class CellBroadcastAlertService extends Service {
}
// Check if we need to perform language filtering.
- CellBroadcastChannelRange range = CellBroadcastChannelManager
- .getCellBroadcastChannelRangeFromMessage(getApplicationContext(), cbm);
+ CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager(mContext,
+ cbm.getSubId(mContext));
+ CellBroadcastChannelRange range = channelManager
+ .getCellBroadcastChannelRangeFromMessage(cbm);
if (range != null && range.mFilterLanguage) {
// If the message's language does not match device's message, we don't display the
// message.
@@ -316,12 +317,7 @@ public class CellBroadcastAlertService extends Service {
}
final CellBroadcastMessage cbm = new CellBroadcastMessage(message);
- int subId = intent.getExtras().getInt(PhoneConstants.SUBSCRIPTION_KEY);
- if (SubscriptionManager.isValidSubscriptionId(subId)) {
- cbm.setSubId(subId);
- } else {
- Log.e(TAG, "Invalid subscription id");
- }
+ int subId = cbm.getSubId(mContext);
if (!shouldDisplayMessage(cbm)) {
return;
@@ -329,8 +325,7 @@ public class CellBroadcastAlertService extends Service {
// Check if message body should be used for duplicate detection.
boolean shouldCompareMessageBody =
- CellBroadcastSettings.getResourcesForDefaultSmsSubscriptionId(
- getApplicationContext())
+ CellBroadcastSettings.getResources(mContext, cbm.getSubId(mContext))
.getBoolean(R.bool.duplicate_compare_body);
int hashCode = shouldCompareMessageBody ? message.getMessageBody().hashCode() : 0;
@@ -417,7 +412,9 @@ public class CellBroadcastAlertService extends Service {
return;
}
- if (CellBroadcastChannelManager.isEmergencyMessage(this, cbm)) {
+ CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager(
+ mContext, cbm.getSubId(mContext));
+ if (channelManager.isEmergencyMessage(cbm)) {
// start alert sound / vibration / TTS and display full-screen alert
openEmergencyAlertNotification(cbm);
} else {
@@ -465,108 +462,109 @@ public class CellBroadcastAlertService extends Service {
// Check if the messages are on additional channels enabled by the resource config.
// If those channels are enabled by the carrier, but the device is actually roaming, we
// should not allow the messages.
- ArrayList<CellBroadcastChannelRange> ranges =
- CellBroadcastChannelManager.getCellBroadcastChannelRanges(
- getApplicationContext(), R.array.additional_cbs_channels_strings);
-
- if (ranges != null) {
- for (CellBroadcastChannelRange range : ranges) {
- if (range.mStartId <= channel && range.mEndId >= channel) {
- // Check if the channel is within the scope. If not, ignore the alert message.
- if (!CellBroadcastChannelManager.checkScope(getApplicationContext(),
- message.getSubId(), range.mScope)) {
- Log.d(TAG, "The range [" + range.mStartId + "-" + range.mEndId
- + "] is not within the scope. mScope = " + range.mScope);
- return false;
- }
+ CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager(
+ mContext, message.getSubId(mContext));
+ ArrayList<CellBroadcastChannelRange> ranges = channelManager.getCellBroadcastChannelRanges(
+ R.array.additional_cbs_channels_strings);
+
+ for (CellBroadcastChannelRange range : ranges) {
+ if (range.mStartId <= channel && range.mEndId >= channel) {
+ // Check if the channel is within the scope. If not, ignore the alert message.
+ if (!channelManager.checkScope(range.mScope)) {
+ Log.d(TAG, "The range [" + range.mStartId + "-" + range.mEndId
+ + "] is not within the scope. mScope = " + range.mScope);
+ return false;
+ }
- // The area update information cell broadcast should not cause any pop-up.
- // Instead the setting's app SIM status will show its information.
- if (range.mAlertType == AlertType.AREA) {
- if (enableAreaUpdateInfoAlerts) {
- // save latest area info broadcast for Settings display and send as
- // broadcast.
- CellBroadcastReceiverApp.setLatestAreaInfo(message);
- Intent intent = new Intent(CB_AREA_INFO_RECEIVED_ACTION);
- intent.setPackage(SETTINGS_APP);
- intent.putExtra(EXTRA_MESSAGE, message);
- // Send broadcast twice, once for apps that have PRIVILEGED permission
- // and once for those that have the runtime one.
- sendBroadcastAsUser(intent, UserHandle.ALL,
- android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
- sendBroadcastAsUser(intent, UserHandle.ALL,
- android.Manifest.permission.READ_PHONE_STATE);
- // area info broadcasts are displayed in Settings status screen
- }
- return false;
- } else if (range.mAlertType == AlertType.TEST) {
- return emergencyAlertEnabled
- && PreferenceManager.getDefaultSharedPreferences(this)
- .getBoolean(CellBroadcastSettings.KEY_ENABLE_TEST_ALERTS,
- false);
+ // The area update information cell broadcast should not cause any pop-up.
+ // Instead the setting's app SIM status will show its information.
+ if (range.mAlertType == AlertType.AREA) {
+ if (enableAreaUpdateInfoAlerts) {
+ // save latest area info broadcast for Settings display and send as
+ // broadcast.
+ CellBroadcastReceiverApp.setLatestAreaInfo(message);
+ Intent intent = new Intent(CB_AREA_INFO_RECEIVED_ACTION);
+ intent.setPackage(SETTINGS_APP);
+ intent.putExtra(EXTRA_MESSAGE, message.getSmsCbMessage());
+ // Send broadcast twice, once for apps that have PRIVILEGED permission
+ // and once for those that have the runtime one.
+ sendBroadcastAsUser(intent, UserHandle.ALL,
+ android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+ sendBroadcastAsUser(intent, UserHandle.ALL,
+ android.Manifest.permission.READ_PHONE_STATE);
+ // area info broadcasts are displayed in Settings status screen
}
-
- return emergencyAlertEnabled;
+ return false;
+ } else if (range.mAlertType == AlertType.TEST) {
+ return emergencyAlertEnabled
+ && PreferenceManager.getDefaultSharedPreferences(this)
+ .getBoolean(CellBroadcastSettings.KEY_ENABLE_TEST_ALERTS,
+ false);
}
+
+ return emergencyAlertEnabled;
}
}
- int subId = message.getSubId();
- if (CellBroadcastChannelManager.checkCellBroadcastChannelRange(subId,
- channel, R.array.emergency_alerts_channels_range_strings, this)) {
+
+ if (channelManager.checkCellBroadcastChannelRange(channel,
+ R.array.emergency_alerts_channels_range_strings)) {
return emergencyAlertEnabled
&& PreferenceManager.getDefaultSharedPreferences(this).getBoolean(
CellBroadcastSettings.KEY_ENABLE_EMERGENCY_ALERTS, true);
}
// CMAS warning types
- if (CellBroadcastChannelManager.checkCellBroadcastChannelRange(subId,
- channel, R.array.cmas_presidential_alerts_channels_range_strings, this)) {
+ if (channelManager.checkCellBroadcastChannelRange(channel,
+ R.array.cmas_presidential_alerts_channels_range_strings)) {
// always enabled
return true;
}
- if (CellBroadcastChannelManager.checkCellBroadcastChannelRange(subId,
- channel, R.array.cmas_alert_extreme_channels_range_strings, this)) {
+ if (channelManager.checkCellBroadcastChannelRange(channel,
+ R.array.cmas_alert_extreme_channels_range_strings)) {
return emergencyAlertEnabled
&& PreferenceManager.getDefaultSharedPreferences(this).getBoolean(
CellBroadcastSettings.KEY_ENABLE_CMAS_EXTREME_THREAT_ALERTS, true);
}
- if (CellBroadcastChannelManager.checkCellBroadcastChannelRange(subId,
- channel, R.array.cmas_alerts_severe_range_strings, this)) {
+ if (channelManager.checkCellBroadcastChannelRange(channel,
+ R.array.cmas_alerts_severe_range_strings)) {
return emergencyAlertEnabled
&& PreferenceManager.getDefaultSharedPreferences(this).getBoolean(
CellBroadcastSettings.KEY_ENABLE_CMAS_SEVERE_THREAT_ALERTS, true);
}
- if (CellBroadcastChannelManager.checkCellBroadcastChannelRange(subId,
- channel, R.array.cmas_amber_alerts_channels_range_strings, this)) {
+ if (channelManager.checkCellBroadcastChannelRange(channel,
+ R.array.cmas_amber_alerts_channels_range_strings)) {
return emergencyAlertEnabled
&& PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean(CellBroadcastSettings.KEY_ENABLE_CMAS_AMBER_ALERTS, true);
}
- if (CellBroadcastChannelManager.checkCellBroadcastChannelRange(subId,
- channel, R.array.required_monthly_test_range_strings, this)
- || CellBroadcastChannelManager.checkCellBroadcastChannelRange(subId,
- channel, R.array.exercise_alert_range_strings, this)
- || CellBroadcastChannelManager.checkCellBroadcastChannelRange(subId,
- channel, R.array.operator_defined_alert_range_strings, this)) {
+ if (channelManager.checkCellBroadcastChannelRange(channel,
+ R.array.required_monthly_test_range_strings)
+ || channelManager.checkCellBroadcastChannelRange(channel,
+ R.array.exercise_alert_range_strings)
+ || channelManager.checkCellBroadcastChannelRange(channel,
+ R.array.operator_defined_alert_range_strings)) {
return emergencyAlertEnabled
&& PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean(CellBroadcastSettings.KEY_ENABLE_TEST_ALERTS,
false);
}
- if (CellBroadcastChannelManager.checkCellBroadcastChannelRange(subId,
- channel, R.array.public_safety_messages_channels_range_strings, this)) {
+
+ if (channelManager.checkCellBroadcastChannelRange(channel,
+ R.array.public_safety_messages_channels_range_strings)) {
return emergencyAlertEnabled
&& PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean(CellBroadcastSettings.KEY_ENABLE_PUBLIC_SAFETY_MESSAGES,
true);
}
- if (CellBroadcastChannelManager.checkCellBroadcastChannelRange(subId,
- channel, R.array.state_local_test_alert_range_strings, this)) {
+
+ if (channelManager.checkCellBroadcastChannelRange(channel,
+ R.array.state_local_test_alert_range_strings)) {
return emergencyAlertEnabled
&& PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean(CellBroadcastSettings.KEY_ENABLE_STATE_LOCAL_TEST_ALERTS,
false);
}
+
return true;
}
@@ -584,6 +582,9 @@ public class CellBroadcastAlertService extends Service {
audioIntent.setAction(CellBroadcastAlertAudio.ACTION_START_ALERT_AUDIO);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager(
+ mContext, message.getSubId(mContext));
+
AlertType alertType = AlertType.DEFAULT;
if (message.isEtwsMessage()) {
alertType = AlertType.ETWS_DEFAULT;
@@ -609,27 +610,31 @@ public class CellBroadcastAlertService extends Service {
}
} else {
int channel = message.getServiceCategory();
- ArrayList<CellBroadcastChannelRange> ranges = CellBroadcastChannelManager
- .getAllCellBroadcastChannelRanges(getApplicationContext());
- if (ranges != null) {
- for (CellBroadcastChannelRange range : ranges) {
- if (channel >= range.mStartId && channel <= range.mEndId) {
- alertType = range.mAlertType;
- break;
- }
+ ArrayList<CellBroadcastChannelRange> ranges = channelManager
+ .getAllCellBroadcastChannelRanges();
+ for (CellBroadcastChannelRange range : ranges) {
+ if (channel >= range.mStartId && channel <= range.mEndId) {
+ alertType = range.mAlertType;
+ break;
}
}
}
- CellBroadcastChannelRange range = CellBroadcastChannelManager
- .getCellBroadcastChannelRangeFromMessage(getApplicationContext(), message);
+ CellBroadcastChannelRange range = channelManager
+ .getCellBroadcastChannelRangeFromMessage(message);
audioIntent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_TONE_TYPE, alertType);
audioIntent.putExtra(
CellBroadcastAlertAudio.ALERT_AUDIO_VIBRATION_PATTERN_EXTRA,
(range != null)
? range.mVibrationPattern
- : CellBroadcastSettings.getResourcesForDefaultSmsSubscriptionId(
- getApplicationContext())
- .getIntArray(R.array.default_vibration_pattern));
+ : CellBroadcastSettings.getResources(mContext, message.getSubId(mContext))
+ .getIntArray(R.array.default_vibration_pattern));
+
+ Resources res = CellBroadcastSettings.getResources(mContext, message.getSubId(mContext));
+ if ((res.getBoolean(R.bool.full_volume_presidential_alert)
+ && message.getCmasMessageClass() == SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT)
+ || prefs.getBoolean(CellBroadcastSettings.KEY_USE_FULL_VOLUME, false)) {
+ audioIntent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_FULL_VOLUME_EXTRA, true);
+ }
String messageBody = message.getMessageBody();
@@ -642,6 +647,9 @@ public class CellBroadcastAlertService extends Service {
audioIntent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_MESSAGE_LANGUAGE,
language);
}
+
+ audioIntent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_SUB_INDEX,
+ message.getSubId(mContext));
startService(audioIntent);
ArrayList<CellBroadcastMessage> messageList = new ArrayList<CellBroadcastMessage>(1);
@@ -667,7 +675,7 @@ public class CellBroadcastAlertService extends Service {
static void addToNotificationBar(CellBroadcastMessage message,
ArrayList<CellBroadcastMessage> messageList, Context context,
boolean fromSaveState) {
- Resources res = CellBroadcastSettings.getResourcesForDefaultSmsSubscriptionId(context);
+ Resources res = CellBroadcastSettings.getResources(context, message.getSubId(context));
int channelTitleId = CellBroadcastResources.getDialogTitleResource(context, message);
CharSequence channelName = context.getText(channelTitleId);
String messageBody = message.getMessageBody();
@@ -696,7 +704,10 @@ public class CellBroadcastAlertService extends Service {
pi = PendingIntent.getActivity(context, NOTIFICATION_ID, intent,
PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
}
- final String channelId = CellBroadcastChannelManager.isEmergencyMessage(context, message)
+ CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager(
+ context, message.getSubId(context));
+
+ final String channelId = channelManager.isEmergencyMessage(message)
? NOTIFICATION_CHANNEL_EMERGENCY_ALERTS : NOTIFICATION_CHANNEL_NON_EMERGENCY_ALERTS;
boolean nonSwipeableNotification = message.isEmergencyAlertMessage()
@@ -745,7 +756,7 @@ public class CellBroadcastAlertService extends Service {
// addToNotification for the emergency display on FEATURE WATCH devices vs the
// Alert Dialog, it will call this and override the emergency audio tone.
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
- && !CellBroadcastChannelManager.isEmergencyMessage(context, message)) {
+ && !channelManager.isEmergencyMessage(message)) {
if (res.getBoolean(R.bool.watch_enable_non_emergency_audio)) {
// start audio/vibration/speech service for non emergency alerts
Intent audioIntent = new Intent(context, CellBroadcastAlertAudio.class);
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastAreaInfoReceiver.java b/src/com/android/cellbroadcastreceiver/CellBroadcastAreaInfoReceiver.java
index d1a9a2df1..51bdc4c80 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastAreaInfoReceiver.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastAreaInfoReceiver.java
@@ -20,7 +20,6 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.UserHandle;
-import android.telephony.CellBroadcastMessage;
import android.util.Log;
public class CellBroadcastAreaInfoReceiver extends BroadcastReceiver {
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastChannelManager.java b/src/com/android/cellbroadcastreceiver/CellBroadcastChannelManager.java
index fcc51d3f8..a41a02eb6 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastChannelManager.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastChannelManager.java
@@ -18,9 +18,9 @@ package com.android.cellbroadcastreceiver;
import static android.telephony.ServiceState.ROAMING_TYPE_NOT_ROAMING;
+import android.annotation.NonNull;
import android.content.Context;
import android.telephony.AccessNetworkConstants;
-import android.telephony.CellBroadcastMessage;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.ServiceState;
import android.telephony.SmsManager;
@@ -69,6 +69,10 @@ public class CellBroadcastChannelManager {
private static ArrayList<CellBroadcastChannelRange> sAllCellBroadcastChannelRanges = null;
+ private final Context mContext;
+
+ private final int mSubId;
+
/**
* Cell broadcast channel range
* A range is consisted by starting channel id, ending channel id, and the alert type
@@ -109,14 +113,14 @@ public class CellBroadcastChannelManager {
public int[] mVibrationPattern;
public boolean mFilterLanguage;
- public CellBroadcastChannelRange(Context context, String channelRange) throws Exception {
+ public CellBroadcastChannelRange(Context context, int subId, String channelRange) {
mAlertType = AlertType.DEFAULT;
mEmergencyLevel = LEVEL_UNKNOWN;
mRat = SmsManager.CELL_BROADCAST_RAN_TYPE_GSM;
mScope = SCOPE_UNKNOWN;
mVibrationPattern =
- CellBroadcastSettings.getResourcesForDefaultSmsSubscriptionId(context)
+ CellBroadcastSettings.getResources(context, subId)
.getIntArray(R.array.default_vibration_pattern);
mFilterLanguage = false;
@@ -130,7 +134,6 @@ public class CellBroadcastChannelManager {
if (tokens.length == 2) {
String key = tokens[0].trim();
String value = tokens[1].trim();
- if (value == null) continue;
switch (key) {
case KEY_TYPE:
mAlertType = AlertType.valueOf(value.toUpperCase());
@@ -158,7 +161,7 @@ public class CellBroadcastChannelManager {
break;
case KEY_VIBRATION:
String[] vibration = value.split("\\|");
- if (vibration != null && vibration.length > 0) {
+ if (vibration.length > 0) {
mVibrationPattern = new int[vibration.length];
for (int i = 0; i < vibration.length; i++) {
mVibrationPattern[i] = Integer.parseInt(vibration[i]);
@@ -197,25 +200,33 @@ public class CellBroadcastChannelManager {
}
/**
+ * Constructor
+ *
+ * @param context Context
+ * @param subId Subscription index
+ */
+ public CellBroadcastChannelManager(Context context, int subId) {
+ mContext = context;
+ mSubId = subId;
+ }
+
+ /**
* Get cell broadcast channels enabled by the carriers from resource key
- * @param context Application context
+ *
* @param key Resource key
+ *
* @return The list of channel ranges enabled by the carriers.
*/
- public static ArrayList<CellBroadcastChannelRange> getCellBroadcastChannelRanges(
- Context context, int key) {
+ public @NonNull ArrayList<CellBroadcastChannelRange> getCellBroadcastChannelRanges(int key) {
ArrayList<CellBroadcastChannelRange> result = new ArrayList<>();
String[] ranges =
- CellBroadcastSettings.getResourcesForDefaultSmsSubscriptionId(context)
- .getStringArray(key);
+ CellBroadcastSettings.getResources(mContext, mSubId).getStringArray(key);
- if (ranges != null) {
- for (String range : ranges) {
- try {
- result.add(new CellBroadcastChannelRange(context, range));
- } catch (Exception e) {
- loge("Failed to parse \"" + range + "\". e=" + e);
- }
+ for (String range : ranges) {
+ try {
+ result.add(new CellBroadcastChannelRange(mContext, mSubId, range));
+ } catch (Exception e) {
+ loge("Failed to parse \"" + range + "\". e=" + e);
}
}
@@ -225,17 +236,15 @@ public class CellBroadcastChannelManager {
/**
* Get all cell broadcast channels
*
- * @param context Application context
* @return all cell broadcast channels
*/
- public static ArrayList<CellBroadcastChannelRange> getAllCellBroadcastChannelRanges(
- Context context) {
+ public @NonNull ArrayList<CellBroadcastChannelRange> getAllCellBroadcastChannelRanges() {
if (sAllCellBroadcastChannelRanges != null) return sAllCellBroadcastChannelRanges;
ArrayList<CellBroadcastChannelRange> result = new ArrayList<>();
for (int key : sCellBroadcastRangeResourceKeys) {
- result.addAll(getCellBroadcastChannelRanges(context, key));
+ result.addAll(getCellBroadcastChannelRanges(key));
}
sAllCellBroadcastChannelRanges = result;
@@ -243,64 +252,57 @@ public class CellBroadcastChannelManager {
}
/**
- * @param subId Subscription index
* @param channel Cell broadcast message channel
- * @param context Application context
* @param key Resource key
+ *
* @return {@code TRUE} if the input channel is within the channel range defined from resource.
* return {@code FALSE} otherwise
*/
- public static boolean checkCellBroadcastChannelRange(int subId, int channel, int key,
- Context context) {
- ArrayList<CellBroadcastChannelRange> ranges =
- CellBroadcastChannelManager.getCellBroadcastChannelRanges(context, key);
- if (ranges != null) {
- for (CellBroadcastChannelRange range : ranges) {
- if (channel >= range.mStartId && channel <= range.mEndId) {
- return checkScope(context, subId, range.mScope);
- }
+ public boolean checkCellBroadcastChannelRange(int channel, int key) {
+ ArrayList<CellBroadcastChannelRange> ranges = getCellBroadcastChannelRanges(key);
+
+ for (CellBroadcastChannelRange range : ranges) {
+ if (channel >= range.mStartId && channel <= range.mEndId) {
+ return checkScope(range.mScope);
}
}
+
return false;
}
/**
* Check if the channel scope matches the current network condition.
*
- * @param subId Subscription id
* @param rangeScope Range scope. Must be SCOPE_CARRIER, SCOPE_DOMESTIC, or SCOPE_INTERNATIONAL.
* @return True if the scope matches the current network roaming condition.
*/
- public static boolean checkScope(Context context, int subId, int rangeScope) {
+ public boolean checkScope(int rangeScope) {
if (rangeScope == CellBroadcastChannelRange.SCOPE_UNKNOWN) return true;
- if (context != null) {
- TelephonyManager tm =
- (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
- tm = tm.createForSubscriptionId(subId);
- ServiceState ss = tm.getServiceState();
- if (ss != null) {
- NetworkRegistrationInfo regInfo = ss.getNetworkRegistrationInfo(
- NetworkRegistrationInfo.DOMAIN_CS,
- AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
- if (regInfo != null) {
- if (regInfo.getRegistrationState() ==
- NetworkRegistrationInfo.REGISTRATION_STATE_HOME
- || regInfo.getRegistrationState() ==
- NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING
- || regInfo.isEmergencyEnabled()) {
- int voiceRoamingType = (regInfo != null) ? regInfo.getRoamingType() :
- ROAMING_TYPE_NOT_ROAMING;
- if (voiceRoamingType == ROAMING_TYPE_NOT_ROAMING) {
- return true;
- } else if (voiceRoamingType == ServiceState.ROAMING_TYPE_DOMESTIC
- && rangeScope == CellBroadcastChannelRange.SCOPE_DOMESTIC) {
- return true;
- } else if (voiceRoamingType == ServiceState.ROAMING_TYPE_INTERNATIONAL
- && rangeScope == CellBroadcastChannelRange.SCOPE_INTERNATIONAL) {
- return true;
- }
- return false;
+ TelephonyManager tm =
+ (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ tm = tm.createForSubscriptionId(mSubId);
+ ServiceState ss = tm.getServiceState();
+ if (ss != null) {
+ NetworkRegistrationInfo regInfo = ss.getNetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_CS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+ if (regInfo != null) {
+ if (regInfo.getRegistrationState()
+ == NetworkRegistrationInfo.REGISTRATION_STATE_HOME
+ || regInfo.getRegistrationState()
+ == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING
+ || regInfo.isEmergencyEnabled()) {
+ int voiceRoamingType = regInfo.getRoamingType();
+ if (voiceRoamingType == ROAMING_TYPE_NOT_ROAMING) {
+ return true;
+ } else if (voiceRoamingType == ServiceState.ROAMING_TYPE_DOMESTIC
+ && rangeScope == CellBroadcastChannelRange.SCOPE_DOMESTIC) {
+ return true;
+ } else if (voiceRoamingType == ServiceState.ROAMING_TYPE_INTERNATIONAL
+ && rangeScope == CellBroadcastChannelRange.SCOPE_INTERNATIONAL) {
+ return true;
}
+ return false;
}
}
}
@@ -310,18 +312,23 @@ public class CellBroadcastChannelManager {
/**
* Return corresponding cellbroadcast range where message belong to
- * @param context Application context
+ *
* @param message Cell broadcast message
*/
- public static CellBroadcastChannelRange getCellBroadcastChannelRangeFromMessage(
- Context context, CellBroadcastMessage message) {
- int subId = message.getSubId();
+ public CellBroadcastChannelRange getCellBroadcastChannelRangeFromMessage(
+ CellBroadcastMessage message) {
+ if (mSubId != message.getSubId(mContext)) {
+ Log.e(TAG, "getCellBroadcastChannelRangeFromMessage: This manager is created for "
+ + "sub " + mSubId + ", should not be used for message from sub "
+ + message.getSubId(mContext));
+ }
+
int channel = message.getServiceCategory();
ArrayList<CellBroadcastChannelRange> ranges = null;
for (int key : sCellBroadcastRangeResourceKeys) {
- if (checkCellBroadcastChannelRange(subId, channel, key, context)) {
- ranges = getCellBroadcastChannelRanges(context, key);
+ if (checkCellBroadcastChannelRange(channel, key)) {
+ ranges = getCellBroadcastChannelRanges(key);
break;
}
}
@@ -338,20 +345,25 @@ public class CellBroadcastChannelManager {
/**
* Check if the cell broadcast message is an emergency message or not
- * @param context Application context
+ *
* @param message Cell broadcast message
* @return True if the message is an emergency message, otherwise false.
*/
- public static boolean isEmergencyMessage(Context context, CellBroadcastMessage message) {
+ public boolean isEmergencyMessage(CellBroadcastMessage message) {
if (message == null) {
return false;
}
+ if (mSubId != message.getSubId(mContext)) {
+ Log.e(TAG, "This manager is created for sub " + mSubId
+ + ", should not be used for message from sub " + message.getSubId(mContext));
+ }
+
int id = message.getServiceCategory();
for (int key : sCellBroadcastRangeResourceKeys) {
ArrayList<CellBroadcastChannelRange> ranges =
- getCellBroadcastChannelRanges(context, key);
+ getCellBroadcastChannelRanges(key);
for (CellBroadcastChannelRange range : ranges) {
if (range.mStartId <= id && range.mEndId >= id) {
switch (range.mEmergencyLevel) {
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastConfigService.java b/src/com/android/cellbroadcastreceiver/CellBroadcastConfigService.java
index bea2de515..ac5503310 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastConfigService.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastConfigService.java
@@ -19,6 +19,7 @@ package com.android.cellbroadcastreceiver;
import static com.android.cellbroadcastreceiver.CellBroadcastReceiver.VDBG;
import android.app.IntentService;
+import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
@@ -59,52 +60,22 @@ public class CellBroadcastConfigService extends IntentService {
protected void onHandleIntent(Intent intent) {
if (ACTION_ENABLE_CHANNELS.equals(intent.getAction())) {
try {
-
- SubscriptionManager subManager = SubscriptionManager.from(getApplicationContext());
- int subId = SubscriptionManager.getDefaultSmsSubscriptionId();
- if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
- subId = SubscriptionManager.getDefaultSubscriptionId();
- if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID &&
- subManager != null) {
- int [] subIds = getActiveSubIdList(subManager);
- if (subIds.length != 0) {
- subId = subIds[0];
- }
- }
- }
+ SubscriptionManager subManager = (SubscriptionManager) getApplicationContext()
+ .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
if (subManager != null) {
- // Retrieve all the active sub ids. We only want to enable
- // cell broadcast on the sub we are interested in and we'll disable
- // it on other subs so the users will not receive duplicate messages from
- // multiple carriers (e.g. for multi-sim users).
+ // Retrieve all the active subscription indice and enable cell broadcast
+ // messages on all subs. The duplication detection will be done at the
+ // frameworks.
int [] subIds = getActiveSubIdList(subManager);
- if (subIds.length != 0)
- {
- for (int id : subIds) {
- SmsManager manager = SmsManager.getSmsManagerForSubscriptionId(id);
- if (manager != null) {
- if (id == subId) {
- // Enable cell broadcast messages on this sub.
- log("Enable CellBroadcast on sub " + id);
- setCellBroadcastOnSub(manager, true);
- }
- else {
- // Disable all cell broadcast message on this sub.
- // This is only for multi-sim scenario. For single SIM device
- // we should not reach here.
- log("Disable CellBroadcast on sub " + id);
- setCellBroadcastOnSub(manager, false);
- }
- }
+ if (subIds.length != 0) {
+ for (int subId : subIds) {
+ log("Enable CellBroadcast on sub " + subId);
+ enableCellBroadcastChannels(subId);
}
- }
- else {
+ } else {
// For no sim scenario.
- SmsManager manager = SmsManager.getDefault();
- if (manager != null) {
- setCellBroadcastOnSub(manager, true);
- }
+ enableCellBroadcastChannels(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
}
}
} catch (Exception ex) {
@@ -123,14 +94,13 @@ public class CellBroadcastConfigService extends IntentService {
}
/**
- * Enable/disable cell broadcast messages id on one subscription
- * This includes all ETWS and CMAS alerts.
- * @param manager SMS manager
- * @param enableForSub True if want to enable messages on this sub (e.g default SMS). False
- * will disable all messages
+ * Enable cell broadcast messages channels. Messages can be only received on the
+ * enabled channels.
+ *
+ * @param subId Subscription index
*/
@VisibleForTesting
- public void setCellBroadcastOnSub(SmsManager manager, boolean enableForSub) {
+ public void enableCellBroadcastChannels(int subId) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
@@ -138,14 +108,14 @@ public class CellBroadcastConfigService extends IntentService {
// Note: If enableAlertsMasterToggle is false, it disables ALL emergency broadcasts
// except for CMAS presidential. i.e. to receive CMAS severe alerts, both
// enableAlertsMasterToggle AND enableCmasSevereAlerts must be true.
- boolean enableAlertsMasterToggle = enableForSub && prefs.getBoolean(
+ boolean enableAlertsMasterToggle = prefs.getBoolean(
CellBroadcastSettings.KEY_ENABLE_ALERTS_MASTER_TOGGLE, true);
boolean enableEtwsAlerts = enableAlertsMasterToggle;
// CMAS Presidential must be always on (See 3GPP TS 22.268 Section 6.2) regardless
// user's preference
- boolean enablePresidential = enableForSub;
+ boolean enablePresidential = true;
boolean enableCmasExtremeAlerts = enableAlertsMasterToggle && prefs.getBoolean(
CellBroadcastSettings.KEY_ENABLE_CMAS_EXTREME_THREAT_ALERTS, true);
@@ -174,7 +144,7 @@ public class CellBroadcastConfigService extends IntentService {
boolean enableEmergencyAlerts = enableAlertsMasterToggle && prefs.getBoolean(
CellBroadcastSettings.KEY_ENABLE_EMERGENCY_ALERTS, true);
- boolean enableGeoFencingTriggerMessage = enableForSub;
+ boolean enableGeoFencingTriggerMessage = true;
if (VDBG) {
log("enableAlertsMasterToggle = " + enableAlertsMasterToggle);
@@ -192,124 +162,108 @@ public class CellBroadcastConfigService extends IntentService {
log("enableGeoFencingTriggerMessage = " + enableGeoFencingTriggerMessage);
}
+ CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager(
+ getApplicationContext(), subId);
+
/** Enable CMAS series messages. */
// Enable/Disable Presidential messages.
- setCellBroadcastRange(
- manager,
- enablePresidential,
- CellBroadcastChannelManager.getCellBroadcastChannelRanges(
- this, R.array.cmas_presidential_alerts_channels_range_strings));
+ setCellBroadcastRange(subId, enablePresidential,
+ channelManager.getCellBroadcastChannelRanges(
+ R.array.cmas_presidential_alerts_channels_range_strings));
// Enable/Disable CMAS extreme messages.
- setCellBroadcastRange(
- manager,
- enableCmasExtremeAlerts,
- CellBroadcastChannelManager.getCellBroadcastChannelRanges(
- this, R.array.cmas_alert_extreme_channels_range_strings));
+ setCellBroadcastRange(subId, enableCmasExtremeAlerts,
+ channelManager.getCellBroadcastChannelRanges(
+ R.array.cmas_alert_extreme_channels_range_strings));
// Enable/Disable CMAS severe messages.
- setCellBroadcastRange(
- manager,
- enableCmasSevereAlerts,
- CellBroadcastChannelManager.getCellBroadcastChannelRanges(
- this, R.array.cmas_alerts_severe_range_strings));
+ setCellBroadcastRange(subId, enableCmasSevereAlerts,
+ channelManager.getCellBroadcastChannelRanges(
+ R.array.cmas_alerts_severe_range_strings));
// Enable/Disable CMAS amber alert messages.
- setCellBroadcastRange(
- manager,
- enableCmasAmberAlerts,
- CellBroadcastChannelManager.getCellBroadcastChannelRanges(
- this, R.array.cmas_amber_alerts_channels_range_strings));
+ setCellBroadcastRange(subId, enableCmasAmberAlerts,
+ channelManager.getCellBroadcastChannelRanges(
+ R.array.cmas_amber_alerts_channels_range_strings));
// Enable/Disable test messages.
- setCellBroadcastRange(
- manager,
- enableTestAlerts,
- CellBroadcastChannelManager.getCellBroadcastChannelRanges(
- this, R.array.required_monthly_test_range_strings));
- setCellBroadcastRange(
- manager,
- enableTestAlerts,
- CellBroadcastChannelManager.getCellBroadcastChannelRanges(
- this, R.array.exercise_alert_range_strings));
- setCellBroadcastRange(
- manager,
- enableTestAlerts,
- CellBroadcastChannelManager.getCellBroadcastChannelRanges(
- this, R.array.operator_defined_alert_range_strings));
+ setCellBroadcastRange(subId, enableTestAlerts,
+ channelManager.getCellBroadcastChannelRanges(
+ R.array.required_monthly_test_range_strings));
+
+ setCellBroadcastRange(subId, enableTestAlerts,
+ channelManager.getCellBroadcastChannelRanges(
+ R.array.exercise_alert_range_strings));
+ setCellBroadcastRange(subId, enableTestAlerts,
+ channelManager.getCellBroadcastChannelRanges(
+ R.array.operator_defined_alert_range_strings));
// Enable/Disable GSM ETWS messages.
- setCellBroadcastRange(
- manager,
- enableEtwsAlerts,
- CellBroadcastChannelManager.getCellBroadcastChannelRanges(
- this, R.array.etws_alerts_range_strings));
+ setCellBroadcastRange(subId, enableEtwsAlerts,
+ channelManager.getCellBroadcastChannelRanges(
+ R.array.etws_alerts_range_strings));
// Enable/Disable GSM ETWS test messages.
- setCellBroadcastRange(
- manager,
- enableTestAlerts,
- CellBroadcastChannelManager.getCellBroadcastChannelRanges(
- this, R.array.etws_test_alerts_range_strings));
+ setCellBroadcastRange(subId, enableTestAlerts,
+ channelManager.getCellBroadcastChannelRanges(
+ R.array.etws_test_alerts_range_strings));
// Enable/Disable GSM public safety messages.
- setCellBroadcastRange(
- manager,
- enablePublicSafetyMessagesChannelAlerts,
- CellBroadcastChannelManager.getCellBroadcastChannelRanges(
- this, R.array.public_safety_messages_channels_range_strings));
+ setCellBroadcastRange(subId, enablePublicSafetyMessagesChannelAlerts,
+ channelManager.getCellBroadcastChannelRanges(
+ R.array.public_safety_messages_channels_range_strings));
// Enable/Disable GSM state/local test alerts.
- setCellBroadcastRange(
- manager,
- enableStateLocalTestAlerts,
- CellBroadcastChannelManager.getCellBroadcastChannelRanges(
- this, R.array.state_local_test_alert_range_strings));
+ setCellBroadcastRange(subId, enableStateLocalTestAlerts,
+ channelManager.getCellBroadcastChannelRanges(
+ R.array.state_local_test_alert_range_strings));
// Enable/Disable GSM geo-fencing trigger messages.
- setCellBroadcastRange(
- manager,
- enableGeoFencingTriggerMessage,
- CellBroadcastChannelManager.getCellBroadcastChannelRanges(
- this, R.array.geo_fencing_trigger_messages_range_strings));
+ setCellBroadcastRange(subId, enableGeoFencingTriggerMessage,
+ channelManager.getCellBroadcastChannelRanges(
+ R.array.geo_fencing_trigger_messages_range_strings));
/** Enable non-CMAS series messages. */
- setCellBroadcastRange(
- manager,
- enableEmergencyAlerts,
- CellBroadcastChannelManager.getCellBroadcastChannelRanges(
- this, R.array.emergency_alerts_channels_range_strings));
+ setCellBroadcastRange(subId, enableEmergencyAlerts,
+ channelManager.getCellBroadcastChannelRanges(
+ R.array.emergency_alerts_channels_range_strings));
// Enable/Disable additional channels based on carrier specific requirement.
ArrayList<CellBroadcastChannelRange> ranges =
- CellBroadcastChannelManager.getCellBroadcastChannelRanges(
- this, R.array.additional_cbs_channels_strings);
- if (ranges != null) {
- for (CellBroadcastChannelRange range: ranges) {
- boolean enableAlerts;
- switch (range.mAlertType) {
- case AREA:
- enableAlerts = enableAreaUpdateInfoAlerts;
- break;
- case TEST:
- enableAlerts = enableTestAlerts;
- break;
- default:
- enableAlerts = enableAlertsMasterToggle;
- }
- setCellBroadcastRange(manager, enableAlerts, new ArrayList<>(Arrays.asList(range)));
+ channelManager.getCellBroadcastChannelRanges(
+ R.array.additional_cbs_channels_strings);
+
+ for (CellBroadcastChannelRange range: ranges) {
+ boolean enableAlerts;
+ switch (range.mAlertType) {
+ case AREA:
+ enableAlerts = enableAreaUpdateInfoAlerts;
+ break;
+ case TEST:
+ enableAlerts = enableTestAlerts;
+ break;
+ default:
+ enableAlerts = enableAlertsMasterToggle;
}
+ setCellBroadcastRange(subId, enableAlerts, new ArrayList<>(Arrays.asList(range)));
}
}
/**
* Enable/disable cell broadcast with messages id range
- * @param manager SMS manager
+ * @param subId Subscription index
* @param enable True for enabling cell broadcast with id range, otherwise for disabling.
* @param ranges Cell broadcast id ranges
*/
- private void setCellBroadcastRange(
- SmsManager manager, boolean enable, List<CellBroadcastChannelRange> ranges) {
+ private void setCellBroadcastRange(int subId, boolean enable,
+ List<CellBroadcastChannelRange> ranges) {
+ SmsManager manager;
+ if (subId != SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
+ manager = SmsManager.getSmsManagerForSubscriptionId(subId);
+ } else {
+ manager = SmsManager.getDefault();
+ }
+
if (ranges != null) {
for (CellBroadcastChannelRange range: ranges) {
if (enable) {
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastContentProvider.java b/src/com/android/cellbroadcastreceiver/CellBroadcastContentProvider.java
index d9b2a3f59..c2eae7a05 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastContentProvider.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastContentProvider.java
@@ -29,7 +29,6 @@ import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.os.AsyncTask;
import android.provider.Telephony;
-import android.telephony.CellBroadcastMessage;
import android.text.TextUtils;
import android.util.Log;
@@ -196,7 +195,6 @@ public class CellBroadcastContentProvider extends ContentProvider {
// is not compatible with CMAS carrier requirements and could also cause other emergency
// alerts, e.g. ETWS, to not display if the database is filled with old messages.
// Use duplicate message ID detection in CellBroadcastAlertService instead of DB query.
-
long rowId = db.insert(CellBroadcastDatabaseHelper.TABLE_NAME, null, cv);
if (rowId == -1) {
Log.e(TAG, "failed to insert new broadcast into database");
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastCursorAdapter.java b/src/com/android/cellbroadcastreceiver/CellBroadcastCursorAdapter.java
index d10f63bb6..9641c64fc 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastCursorAdapter.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastCursorAdapter.java
@@ -18,7 +18,6 @@ package com.android.cellbroadcastreceiver;
import android.content.Context;
import android.database.Cursor;
-import android.telephony.CellBroadcastMessage;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastDatabaseHelper.java b/src/com/android/cellbroadcastreceiver/CellBroadcastDatabaseHelper.java
index 4b76ad065..84f8598bb 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastDatabaseHelper.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastDatabaseHelper.java
@@ -55,8 +55,9 @@ public class CellBroadcastDatabaseHelper extends SQLiteOpenHelper {
* Database version 2-9: (reserved for OEM database customization)
* Database version 10: adds ETWS and CMAS columns and CDMA support
* Database version 11: adds delivery time index
+ * Datatbase version 12: add slotIndex
*/
- static final int DATABASE_VERSION = 11;
+ static final int DATABASE_VERSION = 12;
CellBroadcastDatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
@@ -66,6 +67,7 @@ public class CellBroadcastDatabaseHelper extends SQLiteOpenHelper {
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + TABLE_NAME + " ("
+ Telephony.CellBroadcasts._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ + Telephony.CellBroadcasts.SLOT_INDEX + " INTEGER DEFAULT 0,"
+ Telephony.CellBroadcasts.GEOGRAPHICAL_SCOPE + " INTEGER,"
+ Telephony.CellBroadcasts.PLMN + " TEXT,"
+ Telephony.CellBroadcasts.LAC + " INTEGER,"
@@ -165,6 +167,11 @@ public class CellBroadcastDatabaseHelper extends SQLiteOpenHelper {
createDeliveryTimeIndex(db);
oldVersion++;
}
+
+ if (oldVersion == 11) {
+ db.execSQL("ALTER TABLE " + TABLE_NAME + " ADD COLUMN "
+ + Telephony.CellBroadcasts.SLOT_INDEX + " INTEGER DEFAULT 0;");
+ }
}
/**
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastHandler.java b/src/com/android/cellbroadcastreceiver/CellBroadcastHandler.java
deleted file mode 100644
index a37d35b23..000000000
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastHandler.java
+++ /dev/null
@@ -1,442 +0,0 @@
-/*
- * Copyright (C) 2013 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 static android.content.PermissionChecker.PERMISSION_GRANTED;
-import static android.provider.Settings.Secure.CMAS_ADDITIONAL_BROADCAST_PKG;
-
-import android.Manifest;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.Activity;
-import android.app.AppOpsManager;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.PermissionChecker;
-import android.location.Location;
-import android.location.LocationListener;
-import android.location.LocationManager;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.provider.Telephony;
-import android.provider.Telephony.CellBroadcasts;
-import android.telephony.SmsCbMessage;
-import android.telephony.SubscriptionManager;
-import android.text.format.DateUtils;
-import android.util.LocalLog;
-import android.util.Log;
-
-import com.android.internal.telephony.CbGeoUtils.Geometry;
-import com.android.internal.telephony.CbGeoUtils.LatLng;
-import com.android.internal.telephony.metrics.TelephonyMetrics;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Dispatch new Cell Broadcasts to receivers. Acquires a private wakelock until the broadcast
- * completes and our result receiver is called.
- */
-public class CellBroadcastHandler extends WakeLockStateMachine {
- private static final String EXTRA_MESSAGE = "message";
-
- private final LocalLog mLocalLog = new LocalLog(100);
-
- protected static final Uri CELL_BROADCAST_URI = Uri.parse("content://cellbroadcasts_fwk");
-
- /** Uses to request the location update. */
- public final LocationRequester mLocationRequester;
-
- private CellBroadcastHandler(Context context) {
- this("CellBroadcastHandler", context);
- }
-
- protected CellBroadcastHandler(String debugTag, Context context) {
- super(debugTag, context);
- mLocationRequester = new LocationRequester(
- context,
- (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE),
- getHandler().getLooper());
- }
-
- /**
- * Create a new CellBroadcastHandler.
- * @param context the context to use for dispatching Intents
- * @return the new handler
- */
- public static CellBroadcastHandler makeCellBroadcastHandler(Context context) {
- CellBroadcastHandler handler = new CellBroadcastHandler(context);
- handler.start();
- return handler;
- }
-
- /**
- * Handle Cell Broadcast messages from {@code CdmaInboundSmsHandler}.
- * 3GPP-format Cell Broadcast messages sent from radio are handled in the subclass.
- *
- * @param message the message to process
- * @return true if need to wait for geo-fencing or an ordered broadcast was sent.
- */
- @Override
- protected boolean handleSmsMessage(Message message) {
- if (message.obj instanceof SmsCbMessage) {
- handleBroadcastSms((SmsCbMessage) message.obj, message.arg1);
- return true;
- } else {
- loge("handleMessage got object of type: " + message.obj.getClass().getName());
- return false;
- }
- }
-
- /**
- * Dispatch a Cell Broadcast message to listeners.
- * @param message the Cell Broadcast to broadcast
- */
- protected void handleBroadcastSms(SmsCbMessage message, int slotIndex) {
- // Log Cellbroadcast msg received event
- TelephonyMetrics metrics = TelephonyMetrics.getInstance();
- metrics.writeNewCBSms(slotIndex, message.getMessageFormat(),
- message.getMessagePriority(), message.isCmasMessage(), message.isEtwsMessage(),
- message.getServiceCategory(), message.getSerialNumber(),
- System.currentTimeMillis());
-
- // TODO: Database inserting can be time consuming, therefore this should be changed to
- // asynchronous.
- ContentValues cv = message.getContentValues();
- Uri uri = mContext.getContentResolver().insert(CELL_BROADCAST_URI, cv);
-
- if (message.needGeoFencingCheck()) {
- if (DBG) {
- log("Request location update for geo-fencing. serialNumber = "
- + message.getSerialNumber());
- }
-
- requestLocationUpdate(location -> {
- if (location == null) {
- // Broadcast the message directly if the location is not available.
- broadcastMessage(message, uri, slotIndex);
- } else {
- performGeoFencing(message, uri, message.getGeometries(), location, slotIndex);
- }
- }, message.getMaximumWaitingTime());
- } else {
- if (DBG) {
- log("Broadcast the message directly because no geo-fencing required, "
- + "serialNumber = " + message.getSerialNumber()
- + " needGeoFencing = " + message.needGeoFencingCheck());
- }
- broadcastMessage(message, uri, slotIndex);
- }
- }
-
- /**
- * Perform a geo-fencing check for {@code message}. Broadcast the {@code message} if the
- * {@code location} is inside the {@code broadcastArea}.
- * @param message the message need to geo-fencing check
- * @param uri the message's uri
- * @param broadcastArea the broadcast area of the message
- * @param location current location
- */
- protected void performGeoFencing(SmsCbMessage message, Uri uri, List<Geometry> broadcastArea,
- LatLng location, int slotIndex) {
-
- if (DBG) {
- logd("Perform geo-fencing check for message identifier = "
- + message.getServiceCategory()
- + " serialNumber = " + message.getSerialNumber());
- }
-
- for (Geometry geo : broadcastArea) {
- if (geo.contains(location)) {
- broadcastMessage(message, uri, slotIndex);
- return;
- }
- }
-
- if (DBG) {
- logd("Device location is outside the broadcast area "
- + CbGeoUtils.encodeGeometriesToString(broadcastArea));
- }
- }
-
- /**
- * Request a single location update.
- * @param callback a callback will be called when the location is available.
- * @param maximumWaitTimeSec the maximum wait time of this request. If location is not updated
- * within the maximum wait time, {@code callback#onLocationUpadte(null)} will be called.
- */
- protected void requestLocationUpdate(LocationUpdateCallback callback, int maximumWaitTimeSec) {
- mLocationRequester.requestLocationUpdate(callback, maximumWaitTimeSec);
- }
-
- /**
- * Broadcast a list of cell broadcast messages.
- * @param cbMessages a list of cell broadcast message.
- * @param cbMessageUris the corresponding {@link Uri} of the cell broadcast messages.
- */
- protected void broadcastMessage(List<SmsCbMessage> cbMessages, List<Uri> cbMessageUris,
- int slotIndex) {
- for (int i = 0; i < cbMessages.size(); i++) {
- broadcastMessage(cbMessages.get(i), cbMessageUris.get(i), slotIndex);
- }
- }
-
- /**
- * Broadcast the {@code message} to the applications.
- * @param message a message need to broadcast
- * @param messageUri message's uri
- */
- protected void broadcastMessage(@NonNull SmsCbMessage message, @Nullable Uri messageUri,
- int slotIndex) {
- String receiverPermission;
- int appOp;
- String msg;
- Intent intent;
- if (message.isEmergencyMessage()) {
- msg = "Dispatching emergency SMS CB, SmsCbMessage is: " + message;
- log(msg);
- mLocalLog.log(msg);
- intent = new Intent(Telephony.Sms.Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION);
- //Emergency alerts need to be delivered with high priority
- intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- receiverPermission = Manifest.permission.RECEIVE_EMERGENCY_BROADCAST;
- appOp = AppOpsManager.OP_RECEIVE_EMERGECY_SMS;
-
- intent.putExtra(EXTRA_MESSAGE, message);
- SubscriptionManager.putPhoneIdAndSubIdExtra(intent, slotIndex);
-
- if (Build.IS_DEBUGGABLE) {
- // Send additional broadcast intent to the specified package. This is only for sl4a
- // automation tests.
- final String additionalPackage = Settings.Secure.getString(
- mContext.getContentResolver(), CMAS_ADDITIONAL_BROADCAST_PKG);
- if (additionalPackage != null) {
- Intent additionalIntent = new Intent(intent);
- additionalIntent.setPackage(additionalPackage);
- mContext.sendOrderedBroadcastAsUser(additionalIntent, UserHandle.ALL,
- receiverPermission, appOp, null, getHandler(), Activity.RESULT_OK,
- null, null);
- }
- }
-
- String[] pkgs = mContext.getResources().getStringArray(
- com.android.internal.R.array.config_defaultCellBroadcastReceiverPkgs);
- mReceiverCount.addAndGet(pkgs.length);
- for (String pkg : pkgs) {
- // Explicitly send the intent to all the configured cell broadcast receivers.
- intent.setPackage(pkg);
- mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL, receiverPermission,
- appOp, mReceiver, getHandler(), Activity.RESULT_OK, null, null);
- }
- } else {
- msg = "Dispatching SMS CB, SmsCbMessage is: " + message;
- log(msg);
- mLocalLog.log(msg);
- intent = new Intent(Telephony.Sms.Intents.SMS_CB_RECEIVED_ACTION);
- // Send implicit intent since there are various 3rd party carrier apps listen to
- // this intent.
- intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
- receiverPermission = Manifest.permission.RECEIVE_SMS;
- appOp = AppOpsManager.OP_RECEIVE_SMS;
-
- intent.putExtra(EXTRA_MESSAGE, message);
- SubscriptionManager.putPhoneIdAndSubIdExtra(intent, slotIndex);
-
- mReceiverCount.incrementAndGet();
- mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL, receiverPermission, appOp,
- mReceiver, getHandler(), Activity.RESULT_OK, null, null);
- }
-
- if (messageUri != null) {
- ContentValues cv = new ContentValues();
- cv.put(CellBroadcasts.MESSAGE_BROADCASTED, 1);
- mContext.getContentResolver().update(CELL_BROADCAST_URI, cv,
- CellBroadcasts._ID + "=?", new String[] {messageUri.getLastPathSegment()});
- }
- }
-
- @Override
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("CellBroadcastHandler:");
- mLocalLog.dump(fd, pw, args);
- pw.flush();
- }
-
- /** The callback interface of a location request. */
- public interface LocationUpdateCallback {
- /**
- * Call when the location update is available.
- * @param location a location in (latitude, longitude) format, or {@code null} if the
- * location service is not available.
- */
- void onLocationUpdate(@Nullable LatLng location);
- }
-
- private static final class LocationRequester {
- private static final String TAG = LocationRequester.class.getSimpleName();
-
- /**
- * Use as the default maximum wait time if the cell broadcast doesn't specify the value.
- * Most of the location request should be responded within 20 seconds.
- */
- private static final int DEFAULT_MAXIMUM_WAIT_TIME_SEC = 20;
-
- /**
- * Trigger this event when the {@link LocationManager} is not responded within the given
- * time.
- */
- private static final int EVENT_LOCATION_REQUEST_TIMEOUT = 1;
-
- /** Request a single location update. */
- private static final int EVENT_REQUEST_LOCATION_UPDATE = 2;
-
- /**
- * Request location update from network or gps location provider. Network provider will be
- * used if available, otherwise use the gps provider.
- */
- private static final List<String> LOCATION_PROVIDERS = Arrays.asList(
- LocationManager.NETWORK_PROVIDER, LocationManager.GPS_PROVIDER);
-
- private final LocationManager mLocationManager;
- private final Looper mLooper;
- private final List<LocationUpdateCallback> mCallbacks;
- private final Context mContext;
- private Handler mLocationHandler;
-
- LocationRequester(Context context, LocationManager locationManager, Looper looper) {
- mLocationManager = locationManager;
- mLooper = looper;
- mCallbacks = new ArrayList<>();
- mContext = context;
- mLocationHandler = new LocationHandler(looper);
- }
-
- /**
- * Request a single location update. If the location is not available, a callback with
- * {@code null} location will be called immediately.
- *
- * @param callback a callback to the the response when the location is available
- * @param maximumWaitTimeSec the maximum wait time of this request. If location is not
- * updated within the maximum wait time, {@code callback#onLocationUpadte(null)} will be
- * called.
- */
- void requestLocationUpdate(@NonNull LocationUpdateCallback callback,
- int maximumWaitTimeSec) {
- mLocationHandler.obtainMessage(EVENT_REQUEST_LOCATION_UPDATE, maximumWaitTimeSec,
- 0 /* arg2 */, callback).sendToTarget();
- }
-
- private void onLocationUpdate(@Nullable LatLng location) {
- for (LocationUpdateCallback callback : mCallbacks) {
- callback.onLocationUpdate(location);
- }
- mCallbacks.clear();
- }
-
- private void requestLocationUpdateInternal(@NonNull LocationUpdateCallback callback,
- int maximumWaitTimeSec) {
- if (DBG) Log.d(TAG, "requestLocationUpdate");
- if (!isLocationServiceAvailable()) {
- if (DBG) {
- Log.d(TAG, "Can't request location update because of no location permission");
- }
- callback.onLocationUpdate(null);
- return;
- }
-
- if (!mLocationHandler.hasMessages(EVENT_LOCATION_REQUEST_TIMEOUT)) {
- if (maximumWaitTimeSec == SmsCbMessage.MAXIMUM_WAIT_TIME_NOT_SET) {
- maximumWaitTimeSec = DEFAULT_MAXIMUM_WAIT_TIME_SEC;
- }
- mLocationHandler.sendMessageDelayed(
- mLocationHandler.obtainMessage(EVENT_LOCATION_REQUEST_TIMEOUT),
- maximumWaitTimeSec * DateUtils.SECOND_IN_MILLIS);
- }
-
- mCallbacks.add(callback);
-
- for (String provider : LOCATION_PROVIDERS) {
- if (mLocationManager.isProviderEnabled(provider)) {
- mLocationManager.requestSingleUpdate(provider, mLocationListener, mLooper);
- break;
- }
- }
- }
-
- private boolean isLocationServiceAvailable() {
- if (!hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)
- && !hasPermission(Manifest.permission.ACCESS_COARSE_LOCATION)) return false;
- for (String provider : LOCATION_PROVIDERS) {
- if (mLocationManager.isProviderEnabled(provider)) return true;
- }
- return false;
- }
-
- private boolean hasPermission(String permission) {
- return PermissionChecker.checkCallingOrSelfPermissionForDataDelivery(mContext,
- permission) == PERMISSION_GRANTED;
- }
-
- private final LocationListener mLocationListener = new LocationListener() {
- @Override
- public void onLocationChanged(Location location) {
- mLocationHandler.removeMessages(EVENT_LOCATION_REQUEST_TIMEOUT);
- onLocationUpdate(new LatLng(location.getLatitude(), location.getLongitude()));
- }
-
- @Override
- public void onStatusChanged(String provider, int status, Bundle extras) {}
-
- @Override
- public void onProviderEnabled(String provider) {}
-
- @Override
- public void onProviderDisabled(String provider) {}
- };
-
- private final class LocationHandler extends Handler {
- LocationHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case EVENT_LOCATION_REQUEST_TIMEOUT:
- if (DBG) Log.d(TAG, "location request timeout");
- onLocationUpdate(null);
- break;
- case EVENT_REQUEST_LOCATION_UPDATE:
- requestLocationUpdateInternal((LocationUpdateCallback) msg.obj, msg.arg1);
- break;
- default:
- Log.e(TAG, "Unsupported message type " + msg.what);
- }
- }
- }
- }
-}
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastListActivity.java b/src/com/android/cellbroadcastreceiver/CellBroadcastListActivity.java
index dde4042ad..a87d559c2 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastListActivity.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastListActivity.java
@@ -32,7 +32,6 @@ import android.content.Loader;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.Telephony;
-import android.telephony.CellBroadcastMessage;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastListItem.java b/src/com/android/cellbroadcastreceiver/CellBroadcastListItem.java
index 56939b1a0..e4a8656e9 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastListItem.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastListItem.java
@@ -18,7 +18,6 @@ package com.android.cellbroadcastreceiver;
import android.content.Context;
import android.graphics.Typeface;
-import android.telephony.CellBroadcastMessage;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.StyleSpan;
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastMessage.java b/src/com/android/cellbroadcastreceiver/CellBroadcastMessage.java
new file mode 100644
index 000000000..c20a61c91
--- /dev/null
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastMessage.java
@@ -0,0 +1,462 @@
+/*
+ * 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.annotation.NonNull;
+import android.annotation.UnsupportedAppUsage;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.Telephony;
+import android.provider.Telephony.CellBroadcasts;
+import android.telephony.SmsCbCmasInfo;
+import android.telephony.SmsCbEtwsInfo;
+import android.telephony.SmsCbLocation;
+import android.telephony.SmsCbMessage;
+import android.telephony.SubscriptionManager;
+import android.text.format.DateUtils;
+
+/**
+ * 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 the CellBroadcastReceiver app, which exports
+ * the database of previously received broadcasts at "content://cellbroadcasts/".
+ * The "android.permission.READ_CELL_BROADCASTS" permission is required to read
+ * from the ContentProvider, and writes to the database are not allowed.<p>
+ *
+ * Use {@link #createFromCursor} to create CellBroadcastMessage objects from rows
+ * in the database cursor returned by the ContentProvider.
+ *
+ * {@hide}
+ */
+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";
+
+ /** SmsCbMessage. */
+ private final SmsCbMessage mSmsCbMessage;
+
+ private final long mDeliveryTime;
+ private boolean mIsRead;
+
+ /**
+ * get Subscription information
+ *
+ * @hide
+ */
+ public int getSubId(@NonNull Context context) {
+ SubscriptionManager subMgr = (SubscriptionManager) context.getSystemService(
+ Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+ int[] subs = subMgr.getSubscriptionIds(getSlotIndex());
+ return (subs == null || subs.length == 0) ? SubscriptionManager.INVALID_SUBSCRIPTION_ID :
+ subs[0];
+ }
+
+ /**
+ * get slotIndex associated with this message.
+ * Note, cellbraocastMessage can be received without subscription.
+ *
+ * @hide
+ */
+ public int getSlotIndex() {
+ return mSmsCbMessage.getSlotIndex();
+ }
+
+ @UnsupportedAppUsage
+ public CellBroadcastMessage(SmsCbMessage message) {
+ mSmsCbMessage = message;
+ mDeliveryTime = System.currentTimeMillis();
+ mIsRead = false;
+ }
+
+ private CellBroadcastMessage(SmsCbMessage message, long deliveryTime, boolean isRead) {
+ mSmsCbMessage = message;
+ mDeliveryTime = deliveryTime;
+ mIsRead = isRead;
+ }
+
+ private CellBroadcastMessage(Parcel in) {
+ mSmsCbMessage = new SmsCbMessage(in);
+ mDeliveryTime = in.readLong();
+ mIsRead = (in.readInt() != 0);
+ }
+
+ /** Parcelable: no special flags. */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ mSmsCbMessage.writeToParcel(out, flags);
+ out.writeLong(mDeliveryTime);
+ out.writeInt(mIsRead ? 1 : 0);
+ }
+
+ public static final Parcelable.Creator<CellBroadcastMessage> CREATOR
+ = new Parcelable.Creator<CellBroadcastMessage>() {
+ @Override
+ public CellBroadcastMessage createFromParcel(Parcel in) {
+ return new CellBroadcastMessage(in);
+ }
+
+ @Override
+ 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
+ * @throws IllegalArgumentException if one of the required columns is missing
+ */
+ @UnsupportedAppUsage
+ public static CellBroadcastMessage createFromCursor(Cursor cursor) {
+ int geoScope = cursor.getInt(
+ cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.GEOGRAPHICAL_SCOPE));
+ int serialNum = cursor.getInt(
+ cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.SERIAL_NUMBER));
+ int category = cursor.getInt(
+ cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.SERVICE_CATEGORY));
+ String language = cursor.getString(
+ cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.LANGUAGE_CODE));
+ String body = cursor.getString(
+ cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.MESSAGE_BODY));
+ int format = cursor.getInt(
+ cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.MESSAGE_FORMAT));
+ int priority = cursor.getInt(
+ cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.MESSAGE_PRIORITY));
+ int slotIndex = cursor.getInt(
+ cursor.getColumnIndexOrThrow(CellBroadcasts.SLOT_INDEX));
+
+ String plmn;
+ int plmnColumn = cursor.getColumnIndex(Telephony.CellBroadcasts.PLMN);
+ if (plmnColumn != -1 && !cursor.isNull(plmnColumn)) {
+ plmn = cursor.getString(plmnColumn);
+ } else {
+ plmn = null;
+ }
+
+ int lac;
+ int lacColumn = cursor.getColumnIndex(Telephony.CellBroadcasts.LAC);
+ if (lacColumn != -1 && !cursor.isNull(lacColumn)) {
+ lac = cursor.getInt(lacColumn);
+ } else {
+ lac = -1;
+ }
+
+ int cid;
+ int cidColumn = cursor.getColumnIndex(Telephony.CellBroadcasts.CID);
+ if (cidColumn != -1 && !cursor.isNull(cidColumn)) {
+ cid = cursor.getInt(cidColumn);
+ } else {
+ cid = -1;
+ }
+
+ SmsCbLocation location = new SmsCbLocation(plmn, lac, cid);
+
+ SmsCbEtwsInfo etwsInfo;
+ int etwsWarningTypeColumn = cursor.getColumnIndex(
+ Telephony.CellBroadcasts.ETWS_WARNING_TYPE);
+ if (etwsWarningTypeColumn != -1 && !cursor.isNull(etwsWarningTypeColumn)) {
+ int warningType = cursor.getInt(etwsWarningTypeColumn);
+ etwsInfo = new SmsCbEtwsInfo(warningType, false, false, false, null);
+ } else {
+ etwsInfo = null;
+ }
+
+ SmsCbCmasInfo cmasInfo;
+ int cmasMessageClassColumn = cursor.getColumnIndex(
+ Telephony.CellBroadcasts.CMAS_MESSAGE_CLASS);
+ if (cmasMessageClassColumn != -1 && !cursor.isNull(cmasMessageClassColumn)) {
+ int messageClass = cursor.getInt(cmasMessageClassColumn);
+
+ int cmasCategory;
+ int cmasCategoryColumn = cursor.getColumnIndex(
+ Telephony.CellBroadcasts.CMAS_CATEGORY);
+ if (cmasCategoryColumn != -1 && !cursor.isNull(cmasCategoryColumn)) {
+ cmasCategory = cursor.getInt(cmasCategoryColumn);
+ } else {
+ cmasCategory = SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN;
+ }
+
+ int responseType;
+ int cmasResponseTypeColumn = cursor.getColumnIndex(
+ Telephony.CellBroadcasts.CMAS_RESPONSE_TYPE);
+ if (cmasResponseTypeColumn != -1 && !cursor.isNull(cmasResponseTypeColumn)) {
+ responseType = cursor.getInt(cmasResponseTypeColumn);
+ } else {
+ responseType = SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN;
+ }
+
+ int severity;
+ int cmasSeverityColumn = cursor.getColumnIndex(
+ Telephony.CellBroadcasts.CMAS_SEVERITY);
+ if (cmasSeverityColumn != -1 && !cursor.isNull(cmasSeverityColumn)) {
+ severity = cursor.getInt(cmasSeverityColumn);
+ } else {
+ severity = SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN;
+ }
+
+ int urgency;
+ int cmasUrgencyColumn = cursor.getColumnIndex(
+ Telephony.CellBroadcasts.CMAS_URGENCY);
+ if (cmasUrgencyColumn != -1 && !cursor.isNull(cmasUrgencyColumn)) {
+ urgency = cursor.getInt(cmasUrgencyColumn);
+ } else {
+ urgency = SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN;
+ }
+
+ int certainty;
+ int cmasCertaintyColumn = cursor.getColumnIndex(
+ Telephony.CellBroadcasts.CMAS_CERTAINTY);
+ if (cmasCertaintyColumn != -1 && !cursor.isNull(cmasCertaintyColumn)) {
+ certainty = cursor.getInt(cmasCertaintyColumn);
+ } else {
+ certainty = SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN;
+ }
+
+ cmasInfo = new SmsCbCmasInfo(messageClass, cmasCategory, responseType, severity,
+ urgency, certainty);
+ } else {
+ cmasInfo = null;
+ }
+
+ SmsCbMessage msg = new SmsCbMessage(format, geoScope, serialNum, location, category,
+ language, body, priority, etwsInfo, cmasInfo, slotIndex);
+
+ long deliveryTime = cursor.getLong(cursor.getColumnIndexOrThrow(
+ Telephony.CellBroadcasts.DELIVERY_TIME));
+ boolean isRead = (cursor.getInt(cursor.getColumnIndexOrThrow(
+ Telephony.CellBroadcasts.MESSAGE_READ)) != 0);
+
+ return new CellBroadcastMessage(msg, deliveryTime, isRead);
+ }
+
+ /**
+ * Return a ContentValues object for insertion into the database.
+ * @return a new ContentValues object containing this object's data
+ */
+ @UnsupportedAppUsage
+ public ContentValues getContentValues() {
+ ContentValues cv = new ContentValues(16);
+ SmsCbMessage msg = mSmsCbMessage;
+ cv.put(CellBroadcasts.SLOT_INDEX, msg.getSlotIndex());
+ cv.put(Telephony.CellBroadcasts.GEOGRAPHICAL_SCOPE, msg.getGeographicalScope());
+ SmsCbLocation location = msg.getLocation();
+ if (location.getPlmn() != null) {
+ cv.put(Telephony.CellBroadcasts.PLMN, location.getPlmn());
+ }
+ if (location.getLac() != -1) {
+ cv.put(Telephony.CellBroadcasts.LAC, location.getLac());
+ }
+ if (location.getCid() != -1) {
+ cv.put(Telephony.CellBroadcasts.CID, location.getCid());
+ }
+ cv.put(Telephony.CellBroadcasts.SERIAL_NUMBER, msg.getSerialNumber());
+ cv.put(Telephony.CellBroadcasts.SERVICE_CATEGORY, msg.getServiceCategory());
+ cv.put(Telephony.CellBroadcasts.LANGUAGE_CODE, msg.getLanguageCode());
+ cv.put(Telephony.CellBroadcasts.MESSAGE_BODY, msg.getMessageBody());
+ cv.put(Telephony.CellBroadcasts.DELIVERY_TIME, mDeliveryTime);
+ cv.put(Telephony.CellBroadcasts.MESSAGE_READ, mIsRead);
+ cv.put(Telephony.CellBroadcasts.MESSAGE_FORMAT, msg.getMessageFormat());
+ cv.put(Telephony.CellBroadcasts.MESSAGE_PRIORITY, msg.getMessagePriority());
+
+ SmsCbEtwsInfo etwsInfo = mSmsCbMessage.getEtwsWarningInfo();
+ if (etwsInfo != null) {
+ cv.put(Telephony.CellBroadcasts.ETWS_WARNING_TYPE, etwsInfo.getWarningType());
+ }
+
+ SmsCbCmasInfo cmasInfo = mSmsCbMessage.getCmasWarningInfo();
+ if (cmasInfo != null) {
+ cv.put(Telephony.CellBroadcasts.CMAS_MESSAGE_CLASS, cmasInfo.getMessageClass());
+ cv.put(Telephony.CellBroadcasts.CMAS_CATEGORY, cmasInfo.getCategory());
+ cv.put(Telephony.CellBroadcasts.CMAS_RESPONSE_TYPE, cmasInfo.getResponseType());
+ cv.put(Telephony.CellBroadcasts.CMAS_SEVERITY, cmasInfo.getSeverity());
+ cv.put(Telephony.CellBroadcasts.CMAS_URGENCY, cmasInfo.getUrgency());
+ cv.put(Telephony.CellBroadcasts.CMAS_CERTAINTY, cmasInfo.getCertainty());
+ }
+
+ 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;
+ }
+
+ @UnsupportedAppUsage
+ public String getLanguageCode() {
+ return mSmsCbMessage.getLanguageCode();
+ }
+
+ @UnsupportedAppUsage
+ public int getServiceCategory() {
+ return mSmsCbMessage.getServiceCategory();
+ }
+
+ @UnsupportedAppUsage
+ public long getDeliveryTime() {
+ return mDeliveryTime;
+ }
+
+ @UnsupportedAppUsage
+ public String getMessageBody() {
+ return mSmsCbMessage.getMessageBody();
+ }
+
+ @UnsupportedAppUsage
+ public boolean isRead() {
+ return mIsRead;
+ }
+
+ @UnsupportedAppUsage
+ public int getSerialNumber() {
+ return mSmsCbMessage.getSerialNumber();
+ }
+
+ public SmsCbCmasInfo getCmasWarningInfo() {
+ return mSmsCbMessage.getCmasWarningInfo();
+ }
+
+ @UnsupportedAppUsage
+ public SmsCbEtwsInfo getEtwsWarningInfo() {
+ return mSmsCbMessage.getEtwsWarningInfo();
+ }
+
+ /**
+ * 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 mSmsCbMessage.isEmergencyMessage();
+ }
+
+ /**
+ * Returns whether the broadcast is an emergency (PWS) message type,
+ * including test messages and AMBER alerts.
+ *
+ * @return true if the message is PWS type (ETWS or CMAS)
+ */
+ @UnsupportedAppUsage
+ public boolean isEmergencyAlertMessage() {
+ return mSmsCbMessage.isEmergencyMessage();
+ }
+
+ /**
+ * Return whether the broadcast is an ETWS emergency message type.
+ * @return true if the message is ETWS emergency type; false otherwise
+ */
+ @UnsupportedAppUsage
+ public boolean isEtwsMessage() {
+ return mSmsCbMessage.isEtwsMessage();
+ }
+
+ /**
+ * Return whether the broadcast is a CMAS emergency message type.
+ * @return true if the message is CMAS emergency type; false otherwise
+ */
+ @UnsupportedAppUsage
+ public boolean isCmasMessage() {
+ return mSmsCbMessage.isCmasMessage();
+ }
+
+ /**
+ * Return the CMAS message class.
+ * @return the CMAS message class, e.g. {@link SmsCbCmasInfo#CMAS_CLASS_SEVERE_THREAT}, or
+ * {@link SmsCbCmasInfo#CMAS_CLASS_UNKNOWN} if this is not a CMAS alert
+ */
+ public int getCmasMessageClass() {
+ if (mSmsCbMessage.isCmasMessage()) {
+ return mSmsCbMessage.getCmasWarningInfo().getMessageClass();
+ } else {
+ return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN;
+ }
+ }
+
+ /**
+ * 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() {
+ SmsCbEtwsInfo etwsInfo = mSmsCbMessage.getEtwsWarningInfo();
+ return etwsInfo != null && etwsInfo.isPopupAlert();
+ }
+
+ /**
+ * 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() {
+ SmsCbEtwsInfo etwsInfo = mSmsCbMessage.getEtwsWarningInfo();
+ return etwsInfo != null && etwsInfo.isEmergencyUserAlert();
+ }
+
+ /**
+ * Return whether the broadcast is an ETWS test message.
+ * @return true if the message is an ETWS test message; false otherwise
+ */
+ public boolean isEtwsTestMessage() {
+ SmsCbEtwsInfo etwsInfo = mSmsCbMessage.getEtwsWarningInfo();
+ return etwsInfo != null &&
+ etwsInfo.getWarningType() == SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE;
+ }
+
+ /**
+ * 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
+ */
+ public 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
+ */
+ @UnsupportedAppUsage
+ public String getSpokenDateString(Context context) {
+ int flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE;
+ return DateUtils.formatDateTime(context, mDeliveryTime, flags);
+ }
+
+ public SmsCbMessage getSmsCbMessage() {
+ return mSmsCbMessage;
+ }
+}
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastReceiver.java b/src/com/android/cellbroadcastreceiver/CellBroadcastReceiver.java
index c94b96bfa..670f70905 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastReceiver.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastReceiver.java
@@ -108,8 +108,9 @@ public class CellBroadcastReceiver extends BroadcastReceiver {
// rename registered notification channels on locale change
CellBroadcastAlertService.createNotificationChannels(context);
} else if (Intent.ACTION_SERVICE_STATE.equals(action)) {
- if (CellBroadcastSettings.getResourcesForDefaultSmsSubscriptionId(context).getBoolean(
- R.bool.reset_duplicate_detection_on_airplane_mode)) {
+ if (CellBroadcastSettings.getResources(context,
+ SubscriptionManager.DEFAULT_SUBSCRIPTION_ID).getBoolean(
+ R.bool.reset_duplicate_detection_on_airplane_mode)) {
Bundle extras = intent.getExtras();
ServiceState ss = extras.getParcelable(Intent.EXTRA_SERVICE_STATE);
if (ss.getState() == ServiceState.STATE_POWER_OFF) {
@@ -126,9 +127,9 @@ public class CellBroadcastReceiver extends BroadcastReceiver {
String currentIntervalDefault = sp.getString(CURRENT_INTERVAL_DEFAULT, "0");
// If interval default changes, reset the interval to the new default value.
- String newIntervalDefault =
- CellBroadcastSettings.getResourcesForDefaultSmsSubscriptionId(context)
- .getString(R.string.alert_reminder_interval_default_value);
+ String newIntervalDefault = CellBroadcastSettings.getResources(context,
+ SubscriptionManager.DEFAULT_SUBSCRIPTION_ID).getString(
+ R.string.alert_reminder_interval_default_value);
if (!newIntervalDefault.equals(currentIntervalDefault)) {
Log.d(TAG, "Default interval changed from " + currentIntervalDefault + " to " +
newIntervalDefault);
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastReceiverApp.java b/src/com/android/cellbroadcastreceiver/CellBroadcastReceiverApp.java
index 44c1f8427..811a1924f 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastReceiverApp.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastReceiverApp.java
@@ -17,7 +17,6 @@
package com.android.cellbroadcastreceiver;
import android.app.Application;
-import android.telephony.CellBroadcastMessage;
import java.util.ArrayList;
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastResources.java b/src/com/android/cellbroadcastreceiver/CellBroadcastResources.java
index 25952cedb..cf5420a0d 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastResources.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastResources.java
@@ -18,7 +18,6 @@ package com.android.cellbroadcastreceiver;
import android.content.Context;
import android.graphics.Typeface;
-import android.telephony.CellBroadcastMessage;
import android.telephony.SmsCbCmasInfo;
import android.telephony.SmsCbEtwsInfo;
import android.text.Spannable;
@@ -264,22 +263,21 @@ public class CellBroadcastResources {
}
SmsCbCmasInfo cmasInfo = cbm.getCmasWarningInfo();
- int subId = cbm.getSubId();
+ int subId = cbm.getSubId(context);
+ CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager(
+ context, subId);
final int serviceCategory = cbm.getServiceCategory();
- if (CellBroadcastChannelManager.checkCellBroadcastChannelRange(subId,
- serviceCategory,
- R.array.emergency_alerts_channels_range_strings, context)) {
+ if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
+ R.array.emergency_alerts_channels_range_strings)) {
return R.string.pws_other_message_identifiers;
}
// CMAS warning types
- if (CellBroadcastChannelManager.checkCellBroadcastChannelRange(subId,
- serviceCategory,
- R.array.cmas_presidential_alerts_channels_range_strings, context)) {
+ if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
+ R.array.cmas_presidential_alerts_channels_range_strings)) {
return R.string.cmas_presidential_level_alert;
}
- if (CellBroadcastChannelManager.checkCellBroadcastChannelRange(subId,
- serviceCategory,
- R.array.cmas_alert_extreme_channels_range_strings, context)) {
+ if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
+ R.array.cmas_alert_extreme_channels_range_strings)) {
if (cmasInfo.getSeverity() == SmsCbCmasInfo.CMAS_SEVERITY_EXTREME
&& cmasInfo.getUrgency() == SmsCbCmasInfo.CMAS_URGENCY_IMMEDIATE) {
if (cmasInfo.getCertainty() == SmsCbCmasInfo.CMAS_CERTAINTY_OBSERVED) {
@@ -290,40 +288,39 @@ public class CellBroadcastResources {
}
return R.string.cmas_extreme_alert;
}
- if (CellBroadcastChannelManager.checkCellBroadcastChannelRange(subId,
- serviceCategory, R.array.cmas_alerts_severe_range_strings, context)) {
+ if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
+ R.array.cmas_alerts_severe_range_strings)) {
return R.string.cmas_severe_alert;
}
- if (CellBroadcastChannelManager.checkCellBroadcastChannelRange(subId,
- serviceCategory,
- R.array.cmas_amber_alerts_channels_range_strings, context)) {
+ if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
+ R.array.cmas_amber_alerts_channels_range_strings)) {
return R.string.cmas_amber_alert;
}
- if (CellBroadcastChannelManager.checkCellBroadcastChannelRange(subId,
- serviceCategory, R.array.required_monthly_test_range_strings, context)) {
+ if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
+ R.array.required_monthly_test_range_strings)) {
return R.string.cmas_required_monthly_test;
}
- if (CellBroadcastChannelManager.checkCellBroadcastChannelRange(subId,
- serviceCategory, R.array.exercise_alert_range_strings, context)) {
+ if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
+ R.array.exercise_alert_range_strings)) {
return R.string.cmas_exercise_alert;
}
- if (CellBroadcastChannelManager.checkCellBroadcastChannelRange(subId,
- serviceCategory, R.array.operator_defined_alert_range_strings, context)) {
+ if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
+ R.array.operator_defined_alert_range_strings)) {
return R.string.cmas_operator_defined_alert;
}
- if (CellBroadcastChannelManager.checkCellBroadcastChannelRange(subId,
- serviceCategory, R.array.public_safety_messages_channels_range_strings, context)) {
+ if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
+ R.array.public_safety_messages_channels_range_strings)) {
return R.string.public_safety_message;
}
- if (CellBroadcastChannelManager.checkCellBroadcastChannelRange(subId,
- serviceCategory, R.array.state_local_test_alert_range_strings, context)) {
+ if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
+ R.array.state_local_test_alert_range_strings)) {
return R.string.state_local_test_alert;
}
- if (CellBroadcastChannelManager.isEmergencyMessage(context, cbm)) {
+ if (channelManager.isEmergencyMessage(cbm)) {
ArrayList<CellBroadcastChannelRange> ranges =
- CellBroadcastChannelManager.getCellBroadcastChannelRanges(
- context, R.array.additional_cbs_channels_strings);
+ channelManager.getCellBroadcastChannelRanges(
+ R.array.additional_cbs_channels_strings);
if (ranges != null) {
for (CellBroadcastChannelRange range : ranges) {
if (serviceCategory >= range.mStartId && serviceCategory <= range.mEndId) {
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastSearchIndexableProvider.java b/src/com/android/cellbroadcastreceiver/CellBroadcastSearchIndexableProvider.java
index 8a3059c92..2134e4631 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastSearchIndexableProvider.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastSearchIndexableProvider.java
@@ -41,6 +41,7 @@ import android.database.Cursor;
import android.database.MatrixCursor;
import android.provider.SearchIndexableResource;
import android.provider.SearchIndexablesProvider;
+import android.telephony.SubscriptionManager;
import android.text.TextUtils;
import java.util.ArrayList;
@@ -88,8 +89,8 @@ public class CellBroadcastSearchIndexableProvider extends SearchIndexablesProvid
@Override
public Cursor queryRawData(String[] projection) {
MatrixCursor cursor = new MatrixCursor(INDEXABLES_RAW_COLUMNS);
- final Resources res =
- CellBroadcastSettings.getResourcesForDefaultSmsSubscriptionId(getContext());
+ final Resources res = CellBroadcastSettings.getResources(getContext(),
+ SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
Object[] raw = new Object[INDEXABLES_RAW_COLUMNS.length];
raw[COLUMN_INDEX_RAW_TITLE] = res.getString(R.string.sms_cb_settings);
@@ -98,14 +99,15 @@ public class CellBroadcastSearchIndexableProvider extends SearchIndexablesProvid
keywordList.add(res.getString(keywordRes));
}
- if (!CellBroadcastChannelManager.getCellBroadcastChannelRanges(
- this.getContext(),
+ CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager(getContext(),
+ SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
+
+ if (!channelManager.getCellBroadcastChannelRanges(
R.array.public_safety_messages_channels_range_strings).isEmpty()) {
keywordList.add(res.getString(R.string.public_safety_message));
}
- if (!CellBroadcastChannelManager.getCellBroadcastChannelRanges(
- this.getContext(),
+ if (!channelManager.getCellBroadcastChannelRanges(
R.array.state_local_test_alert_range_strings).isEmpty()) {
keywordList.add(res.getString(R.string.state_local_test_alert));
}
@@ -130,7 +132,8 @@ public class CellBroadcastSearchIndexableProvider extends SearchIndexablesProvid
boolean enableDevSettings =
DevelopmentSettingsHelper.isDevelopmentSettingsEnabled(getContext());
- Resources res = CellBroadcastSettings.getResourcesForDefaultSmsSubscriptionId(getContext());
+ Resources res = CellBroadcastSettings.getResources(getContext(),
+ SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
Object[] ref;
ref = new Object[1];
@@ -178,6 +181,13 @@ public class CellBroadcastSearchIndexableProvider extends SearchIndexablesProvid
cursor.addRow(ref);
}
+ if (!Resources.getSystem().getBoolean(R.bool.show_presidential_alerts_in_settings)) {
+ ref = new Object[1];
+ ref[COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE] =
+ CellBroadcastSettings.KEY_ENABLE_CMAS_PRESIDENTIAL_ALERTS;
+ cursor.addRow(ref);
+ }
+
if (!enableDevSettings) {
ref = new Object[1];
ref[COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE] =
@@ -185,6 +195,40 @@ public class CellBroadcastSearchIndexableProvider extends SearchIndexablesProvid
cursor.addRow(ref);
}
+ CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager(getContext(),
+ SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
+ if (channelManager.getCellBroadcastChannelRanges(
+ R.array.cmas_amber_alerts_channels_range_strings).isEmpty()) {
+ ref = new Object[1];
+ ref[COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE] =
+ CellBroadcastSettings.KEY_ENABLE_CMAS_AMBER_ALERTS;
+ cursor.addRow(ref);
+ }
+
+ if (channelManager.getCellBroadcastChannelRanges(
+ R.array.emergency_alerts_channels_range_strings).isEmpty()) {
+ ref = new Object[1];
+ ref[COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE] =
+ CellBroadcastSettings.KEY_ENABLE_EMERGENCY_ALERTS;
+ cursor.addRow(ref);
+ }
+
+ if (channelManager.getCellBroadcastChannelRanges(
+ R.array.public_safety_messages_channels_range_strings).isEmpty()) {
+ ref = new Object[1];
+ ref[COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE] =
+ CellBroadcastSettings.KEY_ENABLE_PUBLIC_SAFETY_MESSAGES;
+ cursor.addRow(ref);
+ }
+
+ if (channelManager.getCellBroadcastChannelRanges(
+ R.array.state_local_test_alert_range_strings).isEmpty()) {
+ ref = new Object[1];
+ ref[COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE] =
+ CellBroadcastSettings.KEY_ENABLE_STATE_LOCAL_TEST_ALERTS;
+ cursor.addRow(ref);
+ }
+
return cursor;
}
}
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastSettings.java b/src/com/android/cellbroadcastreceiver/CellBroadcastSettings.java
index 3f1331e28..67f23f6db 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastSettings.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastSettings.java
@@ -16,6 +16,7 @@
package com.android.cellbroadcastreceiver;
+import android.annotation.NonNull;
import android.app.ActionBar;
import android.app.Activity;
import android.app.Fragment;
@@ -80,6 +81,11 @@ public class CellBroadcastSettings extends Activity {
// Preference category for alert preferences.
public static final String KEY_CATEGORY_ALERT_PREFERENCES = "category_alert_preferences";
+ // Show checkbox for Presidential alerts in settings
+ // Whether to display CMAS presidential alert notifications (always enabled).
+ public static final String KEY_ENABLE_CMAS_PRESIDENTIAL_ALERTS =
+ "enable_cmas_presidential_alerts";
+
// Whether to display CMAS extreme threat notifications (default is enabled).
public static final String KEY_ENABLE_CMAS_EXTREME_THREAT_ALERTS =
"enable_cmas_extreme_threat_alerts";
@@ -182,6 +188,9 @@ public class CellBroadcastSettings extends Activity {
// WATCH
private TwoStatePreference mAlertReminder;
+ // Show checkbox for Presidential alerts in settings
+ private TwoStatePreference mPresidentialCheckBox;
+
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
@@ -223,6 +232,10 @@ public class CellBroadcastSettings extends Activity {
mDevSettingCategory = (PreferenceCategory)
findPreference(KEY_CATEGORY_DEV_SETTINGS);
+ // Show checkbox for Presidential alerts in settings
+ mPresidentialCheckBox = (TwoStatePreference)
+ findPreference(KEY_ENABLE_CMAS_PRESIDENTIAL_ALERTS);
+
if (pm.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
mAlertReminder = (TwoStatePreference)
findPreference(KEY_WATCH_ALERT_REMINDER);
@@ -286,7 +299,8 @@ public class CellBroadcastSettings extends Activity {
boolean enableDevSettings =
DevelopmentSettingsHelper.isDevelopmentSettingsEnabled(getContext());
- Resources res = getResourcesForDefaultSmsSubscriptionId(getContext());
+ Resources res = CellBroadcastSettings.getResources(getContext(),
+ SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
initReminderIntervalList();
boolean emergencyAlertOnOffOptionEnabled = isFeatureEnabled(getContext(),
@@ -309,8 +323,20 @@ public class CellBroadcastSettings extends Activity {
boolean hideTestAlertMenu = CellBroadcastSettings.isFeatureEnabled(getContext(),
CarrierConfigManager.KEY_CARRIER_FORCE_DISABLE_ETWS_CMAS_TEST_BOOL, false);
+ CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager(
+ getContext(), SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
+
+ boolean isTestAlertsAvailable = !channelManager.getCellBroadcastChannelRanges(
+ R.array.required_monthly_test_range_strings).isEmpty()
+ || !channelManager.getCellBroadcastChannelRanges(
+ R.array.exercise_alert_range_strings).isEmpty()
+ || !channelManager.getCellBroadcastChannelRanges(
+ R.array.operator_defined_alert_range_strings).isEmpty()
+ || !channelManager.getCellBroadcastChannelRanges(
+ R.array.etws_test_alerts_range_strings).isEmpty();
+
// Check if we want to hide the test alert toggle.
- if (hideTestAlertMenu || !enableDevSettings || !isTestAlertsAvailable()) {
+ if (hideTestAlertMenu || !enableDevSettings || !isTestAlertsAvailable) {
if (mTestCheckBox != null) {
mAlertCategory.removePreference(mTestCheckBox);
}
@@ -342,9 +368,20 @@ public class CellBroadcastSettings extends Activity {
}
// Remove preferences based on range configurations
- if (CellBroadcastChannelManager.getCellBroadcastChannelRanges(
- this.getContext(),
- R.array.public_safety_messages_channels_range_strings).isEmpty()) {
+ if (channelManager.getCellBroadcastChannelRanges(
+ R.array.cmas_amber_alerts_channels_range_strings).isEmpty()) {
+ // Remove ambert alert
+ if (mAlertCategory != null) {
+ if (mAmberCheckBox != null) {
+ mAlertCategory.removePreference(mAmberCheckBox);
+ }
+ }
+ }
+
+ // Remove preferences based on range configurations
+ if (channelManager.getCellBroadcastChannelRanges(
+ R.array.public_safety_messages_channels_range_strings).isEmpty() ||
+ !res.getBoolean(R.bool.show_public_safety_settings)) {
// Remove public safety messages
if (mAlertCategory != null) {
if (mPublicSafetyMessagesChannelCheckBox != null) {
@@ -353,8 +390,8 @@ public class CellBroadcastSettings extends Activity {
}
}
- if (CellBroadcastChannelManager.getCellBroadcastChannelRanges(
- this.getContext(), R.array.emergency_alerts_channels_range_strings).isEmpty()) {
+ if (channelManager.getCellBroadcastChannelRanges(
+ R.array.emergency_alerts_channels_range_strings).isEmpty()) {
// Remove emergency alert messages
if (mAlertCategory != null) {
if (mEmergencyAlertsCheckBox != null) {
@@ -363,8 +400,7 @@ public class CellBroadcastSettings extends Activity {
}
}
- if (CellBroadcastChannelManager.getCellBroadcastChannelRanges(
- this.getContext(),
+ if (channelManager.getCellBroadcastChannelRanges(
R.array.state_local_test_alert_range_strings).isEmpty()) {
// Remove state local test messages
if (mAlertCategory != null) {
@@ -431,22 +467,18 @@ public class CellBroadcastSettings extends Activity {
}
});
}
- }
- private boolean isTestAlertsAvailable() {
- return !CellBroadcastChannelManager.getCellBroadcastChannelRanges(
- this.getContext(), R.array.required_monthly_test_range_strings).isEmpty()
- || !CellBroadcastChannelManager.getCellBroadcastChannelRanges(
- this.getContext(), R.array.exercise_alert_range_strings).isEmpty()
- || !CellBroadcastChannelManager.getCellBroadcastChannelRanges(
- this.getContext(), R.array.operator_defined_alert_range_strings)
- .isEmpty()
- || !CellBroadcastChannelManager.getCellBroadcastChannelRanges(
- this.getContext(), R.array.etws_test_alerts_range_strings).isEmpty();
+ // Show checkbox for Presidential alerts in settings
+ if (!res.getBoolean(R.bool.show_presidential_alerts_in_settings)) {
+ if (mAlertCategory != null) {
+ mAlertCategory.removePreference(mPresidentialCheckBox);
+ }
+ }
}
private void initReminderIntervalList() {
- Resources res = getResourcesForDefaultSmsSubscriptionId(getContext());
+ Resources res = CellBroadcastSettings.getResources(
+ getContext(), SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
String[] activeValues =
res.getStringArray(R.array.alert_reminder_interval_active_values);
@@ -519,20 +551,11 @@ public class CellBroadcastSettings extends Activity {
}
public static boolean isFeatureEnabled(Context context, String feature, boolean defaultValue) {
- int subId = SubscriptionManager.getDefaultSmsSubscriptionId();
- if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
- subId = SubscriptionManager.getDefaultSubscriptionId();
- if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
- return defaultValue;
- }
- }
-
CarrierConfigManager configManager =
(CarrierConfigManager) context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
if (configManager != null) {
- PersistableBundle carrierConfig = configManager.getConfigForSubId(subId);
-
+ PersistableBundle carrierConfig = configManager.getConfig();
if (carrierConfig != null) {
return carrierConfig.getBoolean(feature, defaultValue);
}
@@ -541,13 +564,18 @@ public class CellBroadcastSettings extends Activity {
return defaultValue;
}
- public static Resources getResourcesForDefaultSmsSubscriptionId(Context context) {
- int subId = SubscriptionManager.getDefaultSmsSubscriptionId();
- if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
- subId = SubscriptionManager.getDefaultSubscriptionId();
- if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
- return context.getResources();
- }
+ /**
+ * Get the device resource based on SIM
+ *
+ * @param context Context
+ * @param subId Subscription index
+ *
+ * @return The resource
+ */
+ public static @NonNull Resources getResources(@NonNull Context context, int subId) {
+ if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID
+ || !SubscriptionManager.isValidSubscriptionId(subId)) {
+ return context.getResources();
}
if (sResourcesCache.containsKey(subId)) {
diff --git a/src/com/android/cellbroadcastreceiver/DefaultCellBroadcastService.java b/src/com/android/cellbroadcastreceiver/DefaultCellBroadcastService.java
deleted file mode 100644
index a72bb188b..000000000
--- a/src/com/android/cellbroadcastreceiver/DefaultCellBroadcastService.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2019 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.telephony.CellBroadcastService;
-import android.util.Log;
-
-/**
- * The default implementation of CellBroadcastService, which is used for handling GSM and CDMA
- * cell broadcast messages.
- */
-public class DefaultCellBroadcastService extends CellBroadcastService {
- private GsmCellBroadcastHandler mGsmCellBroadcastHandler;
-
- private static final String TAG = "DefaultCellBroadcastService";
-
- @Override
- public void onCreate() {
- super.onCreate();
- mGsmCellBroadcastHandler =
- GsmCellBroadcastHandler.makeGsmCellBroadcastHandler(getApplicationContext());
- }
-
- @Override
- public void onGsmCellBroadcastSms(int slotIndex, byte[] message) {
- Log.d(TAG, "onGsmCellBroadcastSms received message on slotId=" + slotIndex);
- mGsmCellBroadcastHandler.onGsmCellBroadcastSms(slotIndex, message);
- }
-
- @Override
- public void onCdmaCellBroadcastSms(int slotIndex, byte[] message) {
- // TODO implement CDMA
- }
-}
diff --git a/src/com/android/cellbroadcastreceiver/GsmCellBroadcastHandler.java b/src/com/android/cellbroadcastreceiver/GsmCellBroadcastHandler.java
deleted file mode 100644
index 6a7d3469d..000000000
--- a/src/com/android/cellbroadcastreceiver/GsmCellBroadcastHandler.java
+++ /dev/null
@@ -1,404 +0,0 @@
-/*
- * Copyright (C) 2013 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 static com.android.internal.telephony.gsm.SmsCbConstants.MESSAGE_ID_CMAS_GEO_FENCING_TRIGGER;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Message;
-import android.provider.Telephony.CellBroadcasts;
-import android.telephony.CellInfo;
-import android.telephony.CellLocation;
-import android.telephony.SmsCbLocation;
-import android.telephony.SmsCbMessage;
-import android.telephony.TelephonyManager;
-import android.telephony.gsm.GsmCellLocation;
-import android.text.format.DateUtils;
-
-import com.android.cellbroadcastreceiver.GsmSmsCbMessage.GeoFencingTriggerMessage;
-import com.android.cellbroadcastreceiver.GsmSmsCbMessage.GeoFencingTriggerMessage.CellBroadcastIdentity;
-import com.android.internal.telephony.CbGeoUtils.Geometry;
-
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-
-/**
- * Handler for 3GPP format Cell Broadcasts. Parent class can also handle CDMA Cell Broadcasts.
- */
-public class GsmCellBroadcastHandler extends CellBroadcastHandler {
- private static final boolean VDBG = false; // log CB PDU data
-
- /** Indicates that a message is not being broadcasted. */
- private static final String MESSAGE_NOT_BROADCASTED = "0";
-
- /** This map holds incomplete concatenated messages waiting for assembly. */
- @UnsupportedAppUsage
- private final HashMap<SmsCbConcatInfo, byte[][]> mSmsCbPageMap =
- new HashMap<>(4);
-
- protected GsmCellBroadcastHandler(Context context) {
- super("GsmCellBroadcastHandler", context);
- }
-
- @Override
- protected void onQuitting() {
- super.onQuitting(); // release wakelock
- }
-
- /**
- * Handle a GSM cell broadcast message passed from the telephony framework.
- * @param message
- */
- public void onGsmCellBroadcastSms(int slotIndex, byte[] message) {
- sendMessage(EVENT_NEW_SMS_MESSAGE, slotIndex, -1, message);
- }
-
- /**
- * Create a new CellBroadcastHandler.
- * @param context the context to use for dispatching Intents
- * @return the new handler
- */
- public static GsmCellBroadcastHandler makeGsmCellBroadcastHandler(Context context) {
- GsmCellBroadcastHandler handler = new GsmCellBroadcastHandler(context);
- handler.start();
- return handler;
- }
-
- /**
- * Find the cell broadcast messages specify by the geo-fencing trigger message and perform a
- * geo-fencing check for these messages.
- * @param geoFencingTriggerMessage the trigger message
- *
- * @return {@code True} if geo-fencing is need for some cell broadcast message.
- */
- private boolean handleGeoFencingTriggerMessage(
- GeoFencingTriggerMessage geoFencingTriggerMessage, int slotIndex) {
- final List<SmsCbMessage> cbMessages = new ArrayList<>();
- final List<Uri> cbMessageUris = new ArrayList<>();
-
- // Only consider the cell broadcast received within 24 hours.
- long lastReceivedTime = System.currentTimeMillis() - DateUtils.DAY_IN_MILLIS;
-
- // Find the cell broadcast message identify by the message identifier and serial number
- // and is not broadcasted.
- String where = CellBroadcasts.SERVICE_CATEGORY + "=? AND "
- + CellBroadcasts.SERIAL_NUMBER + "=? AND "
- + CellBroadcasts.MESSAGE_BROADCASTED + "=? AND "
- + CellBroadcasts.RECEIVED_TIME + ">?";
-
- ContentResolver resolver = mContext.getContentResolver();
- for (CellBroadcastIdentity identity : geoFencingTriggerMessage.cbIdentifiers) {
- try (Cursor cursor = resolver.query(CELL_BROADCAST_URI,
- CellBroadcasts.QUERY_COLUMNS_FWK,
- where,
- new String[] { Integer.toString(identity.messageIdentifier),
- Integer.toString(identity.serialNumber), MESSAGE_NOT_BROADCASTED,
- Long.toString(lastReceivedTime) },
- null /* sortOrder */)) {
- if (cursor != null) {
- while (cursor.moveToNext()) {
- cbMessages.add(SmsCbMessage.createFromCursor(cursor));
- cbMessageUris.add(ContentUris.withAppendedId(CELL_BROADCAST_URI,
- cursor.getInt(cursor.getColumnIndex(CellBroadcasts._ID))));
- }
- }
- }
- }
-
- List<Geometry> commonBroadcastArea = new ArrayList<>();
- if (geoFencingTriggerMessage.shouldShareBroadcastArea()) {
- for (SmsCbMessage msg : cbMessages) {
- if (msg.getGeometries() != null) {
- commonBroadcastArea.addAll(msg.getGeometries());
- }
- }
- }
-
- // ATIS doesn't specify the geo fencing maximum wait time for the cell broadcasts specified
- // in geo fencing trigger message. We will pick the largest maximum wait time among these
- // cell broadcasts.
- int maximumWaitTimeSec = 0;
- for (SmsCbMessage msg : cbMessages) {
- maximumWaitTimeSec = Math.max(maximumWaitTimeSec, msg.getMaximumWaitingTime());
- }
-
- if (DBG) {
- logd("Geo-fencing trigger message = " + geoFencingTriggerMessage);
- for (SmsCbMessage msg : cbMessages) {
- logd(msg.toString());
- }
- }
-
- if (cbMessages.isEmpty()) {
- if (DBG) logd("No CellBroadcast message need to be broadcasted");
- return false;
- }
-
- requestLocationUpdate(location -> {
- if (location == null) {
- // If the location is not available, broadcast the messages directly.
- broadcastMessage(cbMessages, cbMessageUris, slotIndex);
- } else {
- for (int i = 0; i < cbMessages.size(); i++) {
- List<Geometry> broadcastArea = !commonBroadcastArea.isEmpty()
- ? commonBroadcastArea : cbMessages.get(i).getGeometries();
- if (broadcastArea == null || broadcastArea.isEmpty()) {
- broadcastMessage(cbMessages.get(i), cbMessageUris.get(i), slotIndex);
- } else {
- performGeoFencing(cbMessages.get(i), cbMessageUris.get(i), broadcastArea,
- location, slotIndex);
- }
- }
- }
- }, maximumWaitTimeSec);
- return true;
- }
-
- /**
- * Handle 3GPP-format Cell Broadcast messages sent from radio.
- *
- * @param message the message to process
- * @return true if need to wait for geo-fencing or an ordered broadcast was sent.
- */
- @Override
- protected boolean handleSmsMessage(Message message) {
- // For GSM, message.obj should be a byte[]
- int slotIndex = message.arg1;
- if (message.obj instanceof byte[]) {
- byte[] pdu = (byte[]) message.obj;
- SmsCbHeader header = createSmsCbHeader(pdu);
- if (header == null) return false;
-
- if (header.getServiceCategory() == MESSAGE_ID_CMAS_GEO_FENCING_TRIGGER) {
- GeoFencingTriggerMessage triggerMessage =
- GsmSmsCbMessage.createGeoFencingTriggerMessage(pdu);
- if (triggerMessage != null) {
- return handleGeoFencingTriggerMessage(triggerMessage, slotIndex);
- }
- } else {
- SmsCbMessage cbMessage = handleGsmBroadcastSms(header, pdu, slotIndex);
- if (cbMessage != null) {
- handleBroadcastSms(cbMessage, slotIndex);
- return true;
- }
- if (VDBG) log("Not handled GSM broadcasts.");
- }
- }
- return super.handleSmsMessage(message);
- }
-
- // return the cell location from the first returned cell info, prioritizing GSM
- private CellLocation getCellLocation() {
- TelephonyManager tm =
- (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
- List<CellInfo> infos = tm.getAllCellInfo();
- for (CellInfo info : infos) {
- CellLocation cl = info.getCellIdentity().asCellLocation();
- if (cl instanceof GsmCellLocation) {
- return cl;
- }
- }
- // If no GSM, return first in list
- if (infos != null && !infos.isEmpty() && infos.get(0) != null) {
- return infos.get(0).getCellIdentity().asCellLocation();
- }
- return CellLocation.getEmpty();
- }
-
-
- /**
- * Handle 3GPP format SMS-CB message.
- * @param header the cellbroadcast header.
- * @param receivedPdu the received PDUs as a byte[]
- */
- private SmsCbMessage handleGsmBroadcastSms(SmsCbHeader header, byte[] receivedPdu,
- int slotIndex) {
- try {
- if (VDBG) {
- int pduLength = receivedPdu.length;
- for (int i = 0; i < pduLength; i += 8) {
- StringBuilder sb = new StringBuilder("SMS CB pdu data: ");
- for (int j = i; j < i + 8 && j < pduLength; j++) {
- int b = receivedPdu[j] & 0xff;
- if (b < 0x10) {
- sb.append('0');
- }
- sb.append(Integer.toHexString(b)).append(' ');
- }
- log(sb.toString());
- }
- }
-
- if (VDBG) log("header=" + header);
- TelephonyManager tm =
- (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
- // TODO make a systemAPI for getNetworkOperatorForSlotIndex
- String plmn = tm.getNetworkOperatorForPhone(slotIndex);
- int lac = -1;
- int cid = -1;
- CellLocation cl = getCellLocation();
- // Check if cell location is GsmCellLocation. This is required to support
- // dual-mode devices such as CDMA/LTE devices that require support for
- // both 3GPP and 3GPP2 format messages
- if (cl instanceof GsmCellLocation) {
- GsmCellLocation cellLocation = (GsmCellLocation) cl;
- lac = cellLocation.getLac();
- cid = cellLocation.getCid();
- }
-
- SmsCbLocation location;
- switch (header.getGeographicalScope()) {
- case SmsCbMessage.GEOGRAPHICAL_SCOPE_LOCATION_AREA_WIDE:
- location = new SmsCbLocation(plmn, lac, -1);
- break;
-
- case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE:
- case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE:
- location = new SmsCbLocation(plmn, lac, cid);
- break;
-
- case SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE:
- default:
- location = new SmsCbLocation(plmn);
- break;
- }
-
- byte[][] pdus;
- int pageCount = header.getNumberOfPages();
- if (pageCount > 1) {
- // Multi-page message
- SmsCbConcatInfo concatInfo = new SmsCbConcatInfo(header, location);
-
- // Try to find other pages of the same message
- pdus = mSmsCbPageMap.get(concatInfo);
-
- if (pdus == null) {
- // This is the first page of this message, make room for all
- // pages and keep until complete
- pdus = new byte[pageCount][];
-
- mSmsCbPageMap.put(concatInfo, pdus);
- }
-
- if (VDBG) log("pdus size=" + pdus.length);
- // Page parameter is one-based
- pdus[header.getPageIndex() - 1] = receivedPdu;
-
- for (byte[] pdu : pdus) {
- if (pdu == null) {
- // Still missing pages, exit
- log("still missing pdu");
- return null;
- }
- }
-
- // Message complete, remove and dispatch
- mSmsCbPageMap.remove(concatInfo);
- } else {
- // Single page message
- pdus = new byte[1][];
- pdus[0] = receivedPdu;
- }
-
- // Remove messages that are out of scope to prevent the map from
- // growing indefinitely, containing incomplete messages that were
- // never assembled
- Iterator<SmsCbConcatInfo> iter = mSmsCbPageMap.keySet().iterator();
-
- while (iter.hasNext()) {
- SmsCbConcatInfo info = iter.next();
-
- if (!info.matchesLocation(plmn, lac, cid)) {
- iter.remove();
- }
- }
-
- return GsmSmsCbMessage.createSmsCbMessage(mContext, header, location, pdus);
-
- } catch (RuntimeException e) {
- loge("Error in decoding SMS CB pdu", e);
- return null;
- }
- }
-
- private SmsCbHeader createSmsCbHeader(byte[] bytes) {
- try {
- return new SmsCbHeader(bytes);
- } catch (Exception ex) {
- loge("Can't create SmsCbHeader, ex = " + ex.toString());
- return null;
- }
- }
-
- /**
- * Holds all info about a message page needed to assemble a complete concatenated message.
- */
- private static final class SmsCbConcatInfo {
-
- private final SmsCbHeader mHeader;
- private final SmsCbLocation mLocation;
-
- @UnsupportedAppUsage
- SmsCbConcatInfo(SmsCbHeader header, SmsCbLocation location) {
- mHeader = header;
- mLocation = location;
- }
-
- @Override
- public int hashCode() {
- return (mHeader.getSerialNumber() * 31) + mLocation.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof SmsCbConcatInfo) {
- SmsCbConcatInfo other = (SmsCbConcatInfo) obj;
-
- // Two pages match if they have the same serial number (which includes the
- // geographical scope and update number), and both pages belong to the same
- // location (PLMN, plus LAC and CID if these are part of the geographical scope).
- return mHeader.getSerialNumber() == other.mHeader.getSerialNumber()
- && mLocation.equals(other.mLocation);
- }
-
- return false;
- }
-
- /**
- * Compare the location code for this message to the current location code. The match is
- * relative to the geographical scope of the message, which determines whether the LAC
- * and Cell ID are saved in mLocation or set to -1 to match all values.
- *
- * @param plmn the current PLMN
- * @param lac the current Location Area (GSM) or Service Area (UMTS)
- * @param cid the current Cell ID
- * @return true if this message is valid for the current location; false otherwise
- */
- @UnsupportedAppUsage
- public boolean matchesLocation(String plmn, int lac, int cid) {
- return mLocation.isInLocationArea(plmn, lac, cid);
- }
- }
-}
diff --git a/src/com/android/cellbroadcastreceiver/GsmSmsCbMessage.java b/src/com/android/cellbroadcastreceiver/GsmSmsCbMessage.java
deleted file mode 100644
index 240ac2d6e..000000000
--- a/src/com/android/cellbroadcastreceiver/GsmSmsCbMessage.java
+++ /dev/null
@@ -1,509 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cellbroadcastreceiver;
-
-import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE;
-import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI;
-import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_OTHER_EMERGENCY;
-import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE;
-import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_TSUNAMI;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.content.res.Resources;
-import android.telephony.SmsCbLocation;
-import android.telephony.SmsCbMessage;
-import android.util.Pair;
-import android.util.Slog;
-
-import com.android.cellbroadcastreceiver.CbGeoUtils.Circle;
-import com.android.cellbroadcastreceiver.CbGeoUtils.Polygon;
-import com.android.cellbroadcastreceiver.GsmSmsCbMessage.GeoFencingTriggerMessage.CellBroadcastIdentity;
-import com.android.cellbroadcastreceiver.SmsCbHeader.DataCodingScheme;
-import com.android.internal.R;
-import com.android.internal.telephony.CbGeoUtils.Geometry;
-import com.android.internal.telephony.CbGeoUtils.LatLng;
-import com.android.internal.telephony.GsmAlphabet;
-import com.android.internal.telephony.SmsConstants;
-
-import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * Parses a GSM or UMTS format SMS-CB message into an {@link SmsCbMessage} object. The class is
- * public because {@link #createSmsCbMessage(SmsCbLocation, byte[][])} is used by some test cases.
- */
-public class GsmSmsCbMessage {
- private static final String TAG = GsmSmsCbMessage.class.getSimpleName();
-
- private static final char CARRIAGE_RETURN = 0x0d;
-
- private static final int PDU_BODY_PAGE_LENGTH = 82;
-
- /** Utility class with only static methods. */
- private GsmSmsCbMessage() { }
-
- /**
- * Get built-in ETWS primary messages by category. ETWS primary message does not contain text,
- * so we have to show the pre-built messages to the user.
- *
- * @param context Device context
- * @param category ETWS message category defined in SmsCbConstants
- * @return ETWS text message in string. Return an empty string if no match.
- */
- private static String getEtwsPrimaryMessage(Context context, int category) {
- final Resources r = context.getResources();
- switch (category) {
- case ETWS_WARNING_TYPE_EARTHQUAKE:
- return r.getString(R.string.etws_primary_default_message_earthquake);
- case ETWS_WARNING_TYPE_TSUNAMI:
- return r.getString(R.string.etws_primary_default_message_tsunami);
- case ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI:
- return r.getString(R.string.etws_primary_default_message_earthquake_and_tsunami);
- case ETWS_WARNING_TYPE_TEST_MESSAGE:
- return r.getString(R.string.etws_primary_default_message_test);
- case ETWS_WARNING_TYPE_OTHER_EMERGENCY:
- return r.getString(R.string.etws_primary_default_message_others);
- default:
- return "";
- }
- }
-
- /**
- * Create a new SmsCbMessage object from a header object plus one or more received PDUs.
- *
- * @param pdus PDU bytes
- */
- public static SmsCbMessage createSmsCbMessage(Context context, SmsCbHeader header,
- SmsCbLocation location, byte[][] pdus)
- throws IllegalArgumentException {
- long receivedTimeMillis = System.currentTimeMillis();
- if (header.isEtwsPrimaryNotification()) {
- // ETSI TS 23.041 ETWS Primary Notification message
- // ETWS primary message only contains 4 fields including serial number,
- // message identifier, warning type, and warning security information.
- // There is no field for the content/text so we get the text from the resources.
- return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP, header.getGeographicalScope(),
- header.getSerialNumber(), location, header.getServiceCategory(), null,
- getEtwsPrimaryMessage(context, header.getEtwsInfo().getWarningType()),
- SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, header.getEtwsInfo(),
- header.getCmasInfo(), 0, null /* geometries */, receivedTimeMillis);
- } else if (header.isUmtsFormat()) {
- // UMTS format has only 1 PDU
- byte[] pdu = pdus[0];
- Pair<String, String> cbData = parseUmtsBody(header, pdu);
- String language = cbData.first;
- String body = cbData.second;
- int priority = header.isEmergencyMessage() ? SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY
- : SmsCbMessage.MESSAGE_PRIORITY_NORMAL;
- int nrPages = pdu[SmsCbHeader.PDU_HEADER_LENGTH];
- int wacDataOffset = SmsCbHeader.PDU_HEADER_LENGTH
- + 1 // number of pages
- + (PDU_BODY_PAGE_LENGTH + 1) * nrPages; // cb data
-
- // Has Warning Area Coordinates information
- List<Geometry> geometries = null;
- int maximumWaitingTimeSec = 255;
- if (pdu.length > wacDataOffset) {
- try {
- Pair<Integer, List<Geometry>> wac = parseWarningAreaCoordinates(pdu,
- wacDataOffset);
- maximumWaitingTimeSec = wac.first;
- geometries = wac.second;
- } catch (Exception ex) {
- // Catch the exception here, the message will be considered as having no WAC
- // information which means the message will be broadcasted directly.
- Slog.e(TAG, "Can't parse warning area coordinates, ex = " + ex.toString());
- }
- }
-
- return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP,
- header.getGeographicalScope(), header.getSerialNumber(), location,
- header.getServiceCategory(), language, body, priority,
- header.getEtwsInfo(), header.getCmasInfo(), maximumWaitingTimeSec, geometries,
- receivedTimeMillis);
- } else {
- String language = null;
- StringBuilder sb = new StringBuilder();
- for (byte[] pdu : pdus) {
- Pair<String, String> p = parseGsmBody(header, pdu);
- language = p.first;
- sb.append(p.second);
- }
- int priority = header.isEmergencyMessage() ? SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY
- : SmsCbMessage.MESSAGE_PRIORITY_NORMAL;
-
- return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP,
- header.getGeographicalScope(), header.getSerialNumber(), location,
- header.getServiceCategory(), language, sb.toString(), priority,
- header.getEtwsInfo(), header.getCmasInfo(), 0, null /* geometries */,
- receivedTimeMillis);
- }
- }
-
- /**
- * Parse WEA Handset Action Message(WHAM) a.k.a geo-fencing trigger message.
- *
- * WEA Handset Action Message(WHAM) is a cell broadcast service message broadcast by the network
- * to direct devices to perform a geo-fencing check on selected alerts.
- *
- * WEA Handset Action Message(WHAM) requirements from ATIS-0700041 section 4
- * 1. The Warning Message contents of a WHAM shall be in Cell Broadcast(CB) data format as
- * defined in TS 23.041.
- * 2. The Warning Message Contents of WHAM shall be limited to one CB page(max 20 referenced
- * WEA messages).
- * 3. The broadcast area for a WHAM shall be the union of the broadcast areas of the referenced
- * WEA message.
- * @param pdu cell broadcast pdu, including the header
- * @return {@link GeoFencingTriggerMessage} instance
- */
- public static GeoFencingTriggerMessage createGeoFencingTriggerMessage(byte[] pdu) {
- try {
- // Header length + 1(number of page). ATIS-0700041 define the number of page of
- // geo-fencing trigger message is 1.
- int whamOffset = SmsCbHeader.PDU_HEADER_LENGTH + 1;
-
- BitStreamReader bitReader = new BitStreamReader(pdu, whamOffset);
- int type = bitReader.read(4);
- int length = bitReader.read(7);
- // Skip the remained 5 bits
- bitReader.skip();
-
- int messageIdentifierCount = (length - 2) * 8 / 32;
- List<CellBroadcastIdentity> cbIdentifiers = new ArrayList<>();
- for (int i = 0; i < messageIdentifierCount; i++) {
- // Both messageIdentifier and serialNumber are 16 bits integers.
- // ATIS-0700041 Section 5.1.6
- int messageIdentifier = bitReader.read(16);
- int serialNumber = bitReader.read(16);
- cbIdentifiers.add(new CellBroadcastIdentity(messageIdentifier, serialNumber));
- }
- return new GeoFencingTriggerMessage(type, cbIdentifiers);
- } catch (Exception ex) {
- Slog.e(TAG, "create geo-fencing trigger failed, ex = " + ex.toString());
- return null;
- }
- }
-
- /**
- * Parse the broadcast area and maximum wait time from the Warning Area Coordinates TLV.
- *
- * @param pdu Warning Area Coordinates TLV.
- * @param wacOffset the offset of Warning Area Coordinates TLV.
- * @return a pair with the first element is maximum wait time and the second is the broadcast
- * area. The default value of the maximum wait time is 255 which means use the device default
- * value.
- */
- private static Pair<Integer, List<Geometry>> parseWarningAreaCoordinates(
- byte[] pdu, int wacOffset) {
- // little-endian
- int wacDataLength = (pdu[wacOffset + 1] << 8) | pdu[wacOffset];
- int offset = wacOffset + 2;
-
- if (offset + wacDataLength > pdu.length) {
- throw new IllegalArgumentException("Invalid wac data, expected the length of pdu at"
- + "least " + offset + wacDataLength + ", actual is " + pdu.length);
- }
-
- BitStreamReader bitReader = new BitStreamReader(pdu, offset);
-
- int maximumWaitTimeSec = SmsCbMessage.MAXIMUM_WAIT_TIME_NOT_SET;
-
- List<Geometry> geo = new ArrayList<>();
- int remainedBytes = wacDataLength;
- while (remainedBytes > 0) {
- int type = bitReader.read(4);
- int length = bitReader.read(10);
- remainedBytes -= length;
- // Skip the 2 remained bits
- bitReader.skip();
-
- switch (type) {
- case CbGeoUtils.GEO_FENCING_MAXIMUM_WAIT_TIME:
- maximumWaitTimeSec = bitReader.read(8);
- break;
- case CbGeoUtils.GEOMETRY_TYPE_POLYGON:
- List<LatLng> latLngs = new ArrayList<>();
- // Each coordinate is represented by 44 bits integer.
- // ATIS-0700041 5.2.4 Coordinate coding
- int n = (length - 2) * 8 / 44;
- for (int i = 0; i < n; i++) {
- latLngs.add(getLatLng(bitReader));
- }
- // Skip the padding bits
- bitReader.skip();
- geo.add(new Polygon(latLngs));
- break;
- case CbGeoUtils.GEOMETRY_TYPE_CIRCLE:
- LatLng center = getLatLng(bitReader);
- // radius = (wacRadius / 2^6). The unit of wacRadius is km, we use meter as the
- // distance unit during geo-fencing.
- // ATIS-0700041 5.2.5 radius coding
- double radius = (bitReader.read(20) * 1.0 / (1 << 6)) * 1000.0;
- geo.add(new Circle(center, radius));
- break;
- default:
- throw new IllegalArgumentException("Unsupported geoType = " + type);
- }
- }
- return new Pair(maximumWaitTimeSec, geo);
- }
-
- /**
- * The coordinate is (latitude, longitude), represented by a 44 bits integer.
- * The coding is defined in ATIS-0700041 5.2.4
- * @param bitReader
- * @return coordinate (latitude, longitude)
- */
- private static LatLng getLatLng(BitStreamReader bitReader) {
- // wacLatitude = floor(((latitude + 90) / 180) * 2^22)
- // wacLongitude = floor(((longitude + 180) / 360) * 2^22)
- int wacLat = bitReader.read(22);
- int wacLng = bitReader.read(22);
-
- // latitude = wacLatitude * 180 / 2^22 - 90
- // longitude = wacLongitude * 360 / 2^22 -180
- return new LatLng((wacLat * 180.0 / (1 << 22)) - 90, (wacLng * 360.0 / (1 << 22) - 180));
- }
-
- /**
- * Parse and unpack the UMTS body text according to the encoding in the data coding scheme.
- *
- * @param header the message header to use
- * @param pdu the PDU to decode
- * @return a pair of string containing the language and body of the message in order
- */
- private static Pair<String, String> parseUmtsBody(SmsCbHeader header, byte[] pdu) {
- // Payload may contain multiple pages
- int nrPages = pdu[SmsCbHeader.PDU_HEADER_LENGTH];
- String language = header.getDataCodingSchemeStructedData().language;
-
- if (pdu.length < SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1)
- * nrPages) {
- throw new IllegalArgumentException("Pdu length " + pdu.length + " does not match "
- + nrPages + " pages");
- }
-
- StringBuilder sb = new StringBuilder();
-
- for (int i = 0; i < nrPages; i++) {
- // Each page is 82 bytes followed by a length octet indicating
- // the number of useful octets within those 82
- int offset = SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1) * i;
- int length = pdu[offset + PDU_BODY_PAGE_LENGTH];
-
- if (length > PDU_BODY_PAGE_LENGTH) {
- throw new IllegalArgumentException("Page length " + length
- + " exceeds maximum value " + PDU_BODY_PAGE_LENGTH);
- }
-
- Pair<String, String> p = unpackBody(pdu, offset, length,
- header.getDataCodingSchemeStructedData());
- language = p.first;
- sb.append(p.second);
- }
- return new Pair(language, sb.toString());
-
- }
-
- /**
- * Parse and unpack the GSM body text according to the encoding in the data coding scheme.
- * @param header the message header to use
- * @param pdu the PDU to decode
- * @return a pair of string containing the language and body of the message in order
- */
- private static Pair<String, String> parseGsmBody(SmsCbHeader header, byte[] pdu) {
- // Payload is one single page
- int offset = SmsCbHeader.PDU_HEADER_LENGTH;
- int length = pdu.length - offset;
- return unpackBody(pdu, offset, length, header.getDataCodingSchemeStructedData());
- }
-
- /**
- * Unpack body text from the pdu using the given encoding, position and length within the pdu.
- *
- * @param pdu The pdu
- * @param offset Position of the first byte to unpack
- * @param length Number of bytes to unpack
- * @param dcs data coding scheme
- * @return a Pair of Strings containing the language and body of the message
- */
- private static Pair<String, String> unpackBody(byte[] pdu, int offset, int length,
- DataCodingScheme dcs) {
- String body = null;
-
- String language = dcs.language;
- switch (dcs.encoding) {
- case SmsConstants.ENCODING_7BIT:
- body = GsmAlphabet.gsm7BitPackedToString(pdu, offset, length * 8 / 7);
-
- if (dcs.hasLanguageIndicator && body != null && body.length() > 2) {
- // Language is two GSM characters followed by a CR.
- // The actual body text is offset by 3 characters.
- language = body.substring(0, 2);
- body = body.substring(3);
- }
- break;
-
- case SmsConstants.ENCODING_16BIT:
- if (dcs.hasLanguageIndicator && pdu.length >= offset + 2) {
- // Language is two GSM characters.
- // The actual body text is offset by 2 bytes.
- language = GsmAlphabet.gsm7BitPackedToString(pdu, offset, 2);
- offset += 2;
- length -= 2;
- }
-
- try {
- body = new String(pdu, offset, (length & 0xfffe), "utf-16");
- } catch (UnsupportedEncodingException e) {
- // Apparently it wasn't valid UTF-16.
- throw new IllegalArgumentException("Error decoding UTF-16 message", e);
- }
- break;
-
- default:
- break;
- }
-
- if (body != null) {
- // Remove trailing carriage return
- for (int i = body.length() - 1; i >= 0; i--) {
- if (body.charAt(i) != CARRIAGE_RETURN) {
- body = body.substring(0, i + 1);
- break;
- }
- }
- } else {
- body = "";
- }
-
- return new Pair<String, String>(language, body);
- }
-
- /** A class use to facilitate the processing of bits stream data. */
- private static final class BitStreamReader {
- /** The bits stream represent by a bytes array. */
- private final byte[] mData;
-
- /** The offset of the current byte. */
- private int mCurrentOffset;
-
- /**
- * The remained bits of the current byte which have not been read. The most significant
- * will be read first, so the remained bits are always the least significant bits.
- */
- private int mRemainedBit;
-
- /**
- * Constructor
- * @param data bit stream data represent by byte array.
- * @param offset the offset of the first byte.
- */
- BitStreamReader(byte[] data, int offset) {
- mData = data;
- mCurrentOffset = offset;
- mRemainedBit = 8;
- }
-
- /**
- * Read the first {@code count} bits.
- * @param count the number of bits need to read
- * @return {@code bits} represent by an 32-bits integer, therefore {@code count} must be no
- * greater than 32.
- */
- public int read(int count) throws IndexOutOfBoundsException {
- int val = 0;
- while (count > 0) {
- if (count >= mRemainedBit) {
- val <<= mRemainedBit;
- val |= mData[mCurrentOffset] & ((1 << mRemainedBit) - 1);
- count -= mRemainedBit;
- mRemainedBit = 8;
- ++mCurrentOffset;
- } else {
- val <<= count;
- val |= (mData[mCurrentOffset] & ((1 << mRemainedBit) - 1))
- >> (mRemainedBit - count);
- mRemainedBit -= count;
- count = 0;
- }
- }
- return val;
- }
-
- /**
- * Skip the current bytes if the remained bits is less than 8. This is useful when
- * processing the padding or reserved bits.
- */
- public void skip() {
- if (mRemainedBit < 8) {
- mRemainedBit = 8;
- ++mCurrentOffset;
- }
- }
- }
-
- /**
- * Part of a GSM SMS cell broadcast message which may trigger geo-fencing logic.
- * @hide
- */
- public static final class GeoFencingTriggerMessage {
- /**
- * Indicate the list of active alerts share their warning area coordinates which means the
- * broadcast area is the union of the broadcast areas of the active alerts in this list.
- */
- public static final int TYPE_ACTIVE_ALERT_SHARE_WAC = 2;
-
- public final int type;
- public final List<CellBroadcastIdentity> cbIdentifiers;
-
- GeoFencingTriggerMessage(int type, @NonNull List<CellBroadcastIdentity> cbIdentifiers) {
- this.type = type;
- this.cbIdentifiers = cbIdentifiers;
- }
-
- /**
- * Whether the trigger message indicates that the broadcast areas are shared between all
- * active alerts.
- * @return true if broadcast areas are to be shared
- */
- boolean shouldShareBroadcastArea() {
- return type == TYPE_ACTIVE_ALERT_SHARE_WAC;
- }
-
- static final class CellBroadcastIdentity {
- public final int messageIdentifier;
- public final int serialNumber;
- CellBroadcastIdentity(int messageIdentifier, int serialNumber) {
- this.messageIdentifier = messageIdentifier;
- this.serialNumber = serialNumber;
- }
- }
-
- @Override
- public String toString() {
- String identifiers = cbIdentifiers.stream()
- .map(cbIdentifier ->String.format("(msgId = %d, serial = %d)",
- cbIdentifier.messageIdentifier, cbIdentifier.serialNumber))
- .collect(Collectors.joining(","));
- return "triggerType=" + type + " identifiers=" + identifiers;
- }
- }
-}
diff --git a/src/com/android/cellbroadcastreceiver/IState.java b/src/com/android/cellbroadcastreceiver/IState.java
deleted file mode 100644
index 31c2e17b6..000000000
--- a/src/com/android/cellbroadcastreceiver/IState.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/**
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cellbroadcastreceiver;
-
-import android.annotation.UnsupportedAppUsage;
-import android.os.Message;
-
-/**
- * {@hide}
- *
- * The interface for implementing states in a {@link StateMachine}
- */
-public interface IState {
-
- /**
- * Returned by processMessage to indicate the the message was processed.
- */
- boolean HANDLED = true;
-
- /**
- * Returned by processMessage to indicate the the message was NOT processed.
- */
- boolean NOT_HANDLED = false;
-
- /**
- * Called when a state is entered.
- */
- void enter();
-
- /**
- * Called when a state is exited.
- */
- void exit();
-
- /**
- * Called when a message is to be processed by the
- * state machine.
- *
- * This routine is never reentered thus no synchronization
- * is needed as only one processMessage method will ever be
- * executing within a state machine at any given time. This
- * does mean that processing by this routine must be completed
- * as expeditiously as possible as no subsequent messages will
- * be processed until this routine returns.
- *
- * @param msg to process
- * @return HANDLED if processing has completed and NOT_HANDLED
- * if the message wasn't processed.
- */
- boolean processMessage(Message msg);
-
- /**
- * Name of State for debugging purposes.
- *
- * @return name of state.
- */
- @UnsupportedAppUsage
- String getName();
-}
diff --git a/src/com/android/cellbroadcastreceiver/SmsCbHeader.java b/src/com/android/cellbroadcastreceiver/SmsCbHeader.java
deleted file mode 100644
index 6210c8070..000000000
--- a/src/com/android/cellbroadcastreceiver/SmsCbHeader.java
+++ /dev/null
@@ -1,596 +0,0 @@
-/*
- * Copyright (C) 2010 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.telephony.SmsCbCmasInfo;
-import android.telephony.SmsCbEtwsInfo;
-
-import com.android.internal.telephony.SmsConstants;
-import com.android.internal.telephony.gsm.SmsCbConstants;
-
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
-import java.util.Arrays;
-import java.util.Locale;
-
-/**
- * Parses a 3GPP TS 23.041 cell broadcast message header. This class is public for use by
- * CellBroadcastReceiver test cases, but should not be used by applications.
- *
- * All relevant header information is now sent as a Parcelable
- * {@link android.telephony.SmsCbMessage} object in the "message" extra of the
- * {@link android.provider.Telephony.Sms.Intents#SMS_CB_RECEIVED_ACTION} or
- * {@link android.provider.Telephony.Sms.Intents#SMS_EMERGENCY_CB_RECEIVED_ACTION} intent.
- * The raw PDU is no longer sent to SMS CB applications.
- */
-public class SmsCbHeader {
- /**
- * Languages in the 0000xxxx DCS group as defined in 3GPP TS 23.038, section 5.
- */
- private static final String[] LANGUAGE_CODES_GROUP_0 = {
- Locale.GERMAN.getLanguage(), // German
- Locale.ENGLISH.getLanguage(), // English
- Locale.ITALIAN.getLanguage(), // Italian
- Locale.FRENCH.getLanguage(), // French
- new Locale("es").getLanguage(), // Spanish
- new Locale("nl").getLanguage(), // Dutch
- new Locale("sv").getLanguage(), // Swedish
- new Locale("da").getLanguage(), // Danish
- new Locale("pt").getLanguage(), // Portuguese
- new Locale("fi").getLanguage(), // Finnish
- new Locale("nb").getLanguage(), // Norwegian
- new Locale("el").getLanguage(), // Greek
- new Locale("tr").getLanguage(), // Turkish
- new Locale("hu").getLanguage(), // Hungarian
- new Locale("pl").getLanguage(), // Polish
- null
- };
-
- /**
- * Languages in the 0010xxxx DCS group as defined in 3GPP TS 23.038, section 5.
- */
- private static final String[] LANGUAGE_CODES_GROUP_2 = {
- new Locale("cs").getLanguage(), // Czech
- new Locale("he").getLanguage(), // Hebrew
- new Locale("ar").getLanguage(), // Arabic
- new Locale("ru").getLanguage(), // Russian
- new Locale("is").getLanguage(), // Icelandic
- null, null, null, null, null, null, null, null, null, null, null
- };
-
- /**
- * Length of SMS-CB header
- */
- static final int PDU_HEADER_LENGTH = 6;
-
- /**
- * GSM pdu format, as defined in 3gpp TS 23.041, section 9.4.1
- */
- static final int FORMAT_GSM = 1;
-
- /**
- * UMTS pdu format, as defined in 3gpp TS 23.041, section 9.4.2
- */
- static final int FORMAT_UMTS = 2;
-
- /**
- * ETWS pdu format, as defined in 3gpp TS 23.041, section 9.4.1.3
- */
- static final int FORMAT_ETWS_PRIMARY = 3;
-
- /**
- * Message type value as defined in 3gpp TS 25.324, section 11.1.
- */
- private static final int MESSAGE_TYPE_CBS_MESSAGE = 1;
-
- /**
- * Length of GSM pdus
- */
- private static final int PDU_LENGTH_GSM = 88;
-
- /**
- * Maximum length of ETWS primary message GSM pdus
- */
- private static final int PDU_LENGTH_ETWS = 56;
-
- private final int mGeographicalScope;
-
- /** The serial number combines geographical scope, message code, and update number. */
- private final int mSerialNumber;
-
- /** The Message Identifier in 3GPP is the same as the Service Category in CDMA. */
- @UnsupportedAppUsage
- private final int mMessageIdentifier;
-
- private final int mDataCodingScheme;
-
- private final int mPageIndex;
-
- private final int mNrOfPages;
-
- private final int mFormat;
-
- private DataCodingScheme mDataCodingSchemeStructedData;
-
- /** ETWS warning notification info. */
- private final SmsCbEtwsInfo mEtwsInfo;
-
- /** CMAS warning notification info. */
- private final SmsCbCmasInfo mCmasInfo;
-
- @UnsupportedAppUsage
- public SmsCbHeader(byte[] pdu) throws IllegalArgumentException {
- if (pdu == null || pdu.length < PDU_HEADER_LENGTH) {
- throw new IllegalArgumentException("Illegal PDU");
- }
-
- if (pdu.length <= PDU_LENGTH_GSM) {
- // can be ETWS or GSM format.
- // Per TS23.041 9.4.1.2 and 9.4.1.3.2, GSM and ETWS format both
- // contain serial number which contains GS, Message Code, and Update Number
- // per 9.4.1.2.1, and message identifier in same octets
- mGeographicalScope = (pdu[0] & 0xc0) >>> 6;
- mSerialNumber = ((pdu[0] & 0xff) << 8) | (pdu[1] & 0xff);
- mMessageIdentifier = ((pdu[2] & 0xff) << 8) | (pdu[3] & 0xff);
- if (isEtwsMessage() && pdu.length <= PDU_LENGTH_ETWS) {
- mFormat = FORMAT_ETWS_PRIMARY;
- mDataCodingScheme = -1;
- mPageIndex = -1;
- mNrOfPages = -1;
- boolean emergencyUserAlert = (pdu[4] & 0x1) != 0;
- boolean activatePopup = (pdu[5] & 0x80) != 0;
- int warningType = (pdu[4] & 0xfe) >>> 1;
- byte[] warningSecurityInfo;
- // copy the Warning-Security-Information, if present
- if (pdu.length > PDU_HEADER_LENGTH) {
- warningSecurityInfo = Arrays.copyOfRange(pdu, 6, pdu.length);
- } else {
- warningSecurityInfo = null;
- }
- mEtwsInfo = new SmsCbEtwsInfo(warningType, emergencyUserAlert, activatePopup,
- true, warningSecurityInfo);
- mCmasInfo = null;
- return; // skip the ETWS/CMAS initialization code for regular notifications
- } else {
- // GSM pdus are no more than 88 bytes
- mFormat = FORMAT_GSM;
- mDataCodingScheme = pdu[4] & 0xff;
-
- // Check for invalid page parameter
- int pageIndex = (pdu[5] & 0xf0) >>> 4;
- int nrOfPages = pdu[5] & 0x0f;
-
- if (pageIndex == 0 || nrOfPages == 0 || pageIndex > nrOfPages) {
- pageIndex = 1;
- nrOfPages = 1;
- }
-
- mPageIndex = pageIndex;
- mNrOfPages = nrOfPages;
- }
- } else {
- // UMTS pdus are always at least 90 bytes since the payload includes
- // a number-of-pages octet and also one length octet per page
- mFormat = FORMAT_UMTS;
-
- int messageType = pdu[0];
-
- if (messageType != MESSAGE_TYPE_CBS_MESSAGE) {
- throw new IllegalArgumentException("Unsupported message type " + messageType);
- }
-
- mMessageIdentifier = ((pdu[1] & 0xff) << 8) | pdu[2] & 0xff;
- mGeographicalScope = (pdu[3] & 0xc0) >>> 6;
- mSerialNumber = ((pdu[3] & 0xff) << 8) | (pdu[4] & 0xff);
- mDataCodingScheme = pdu[5] & 0xff;
-
- // We will always consider a UMTS message as having one single page
- // since there's only one instance of the header, even though the
- // actual payload may contain several pages.
- mPageIndex = 1;
- mNrOfPages = 1;
- }
-
- if (mDataCodingScheme != -1) {
- mDataCodingSchemeStructedData = new DataCodingScheme(mDataCodingScheme);
- }
-
- if (isEtwsMessage()) {
- boolean emergencyUserAlert = isEtwsEmergencyUserAlert();
- boolean activatePopup = isEtwsPopupAlert();
- int warningType = getEtwsWarningType();
- mEtwsInfo = new SmsCbEtwsInfo(warningType, emergencyUserAlert, activatePopup,
- false, null);
- mCmasInfo = null;
- } else if (isCmasMessage()) {
- int messageClass = getCmasMessageClass();
- int severity = getCmasSeverity();
- int urgency = getCmasUrgency();
- int certainty = getCmasCertainty();
- mEtwsInfo = null;
- mCmasInfo = new SmsCbCmasInfo(messageClass, SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN,
- SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN, severity, urgency, certainty);
- } else {
- mEtwsInfo = null;
- mCmasInfo = null;
- }
- }
-
- @UnsupportedAppUsage
- int getGeographicalScope() {
- return mGeographicalScope;
- }
-
- @UnsupportedAppUsage
- int getSerialNumber() {
- return mSerialNumber;
- }
-
- @UnsupportedAppUsage
- int getServiceCategory() {
- return mMessageIdentifier;
- }
-
- int getDataCodingScheme() {
- return mDataCodingScheme;
- }
-
- DataCodingScheme getDataCodingSchemeStructedData() {
- return mDataCodingSchemeStructedData;
- }
-
- @UnsupportedAppUsage
- int getPageIndex() {
- return mPageIndex;
- }
-
- @UnsupportedAppUsage
- int getNumberOfPages() {
- return mNrOfPages;
- }
-
- SmsCbEtwsInfo getEtwsInfo() {
- return mEtwsInfo;
- }
-
- SmsCbCmasInfo getCmasInfo() {
- return mCmasInfo;
- }
-
- /**
- * Return whether this broadcast is an emergency (PWS) message type.
- * @return true if this message is emergency type; false otherwise
- */
- boolean isEmergencyMessage() {
- return mMessageIdentifier >= SmsCbConstants.MESSAGE_ID_PWS_FIRST_IDENTIFIER
- && mMessageIdentifier <= SmsCbConstants.MESSAGE_ID_PWS_LAST_IDENTIFIER;
- }
-
- /**
- * Return whether this broadcast is an ETWS emergency message type.
- * @return true if this message is ETWS emergency type; false otherwise
- */
- private boolean isEtwsMessage() {
- return (mMessageIdentifier & SmsCbConstants.MESSAGE_ID_ETWS_TYPE_MASK)
- == SmsCbConstants.MESSAGE_ID_ETWS_TYPE;
- }
-
- /**
- * Return whether this broadcast is an ETWS primary notification.
- * @return true if this message is an ETWS primary notification; false otherwise
- */
- boolean isEtwsPrimaryNotification() {
- return mFormat == FORMAT_ETWS_PRIMARY;
- }
-
- /**
- * Return whether this broadcast is in UMTS format.
- * @return true if this message is in UMTS format; false otherwise
- */
- boolean isUmtsFormat() {
- return mFormat == FORMAT_UMTS;
- }
-
- /**
- * Return whether this message is a CMAS emergency message type.
- * @return true if this message is CMAS emergency type; false otherwise
- */
- private boolean isCmasMessage() {
- return mMessageIdentifier >= SmsCbConstants.MESSAGE_ID_CMAS_FIRST_IDENTIFIER
- && mMessageIdentifier <= SmsCbConstants.MESSAGE_ID_CMAS_LAST_IDENTIFIER;
- }
-
- /**
- * Return whether the popup alert flag is set for an ETWS warning notification.
- * This method assumes that the message ID has already been checked for ETWS type.
- *
- * @return true if the message code indicates a popup alert should be displayed
- */
- private boolean isEtwsPopupAlert() {
- return (mSerialNumber & SmsCbConstants.SERIAL_NUMBER_ETWS_ACTIVATE_POPUP) != 0;
- }
-
- /**
- * Return whether the emergency user alert flag is set for an ETWS warning notification.
- * This method assumes that the message ID has already been checked for ETWS type.
- *
- * @return true if the message code indicates an emergency user alert
- */
- private boolean isEtwsEmergencyUserAlert() {
- return (mSerialNumber & SmsCbConstants.SERIAL_NUMBER_ETWS_EMERGENCY_USER_ALERT) != 0;
- }
-
- /**
- * Returns the warning type for an ETWS warning notification.
- * This method assumes that the message ID has already been checked for ETWS type.
- *
- * @return the ETWS warning type defined in 3GPP TS 23.041 section 9.3.24
- */
- private int getEtwsWarningType() {
- return mMessageIdentifier - SmsCbConstants.MESSAGE_ID_ETWS_EARTHQUAKE_WARNING;
- }
-
- /**
- * Returns the message class for a CMAS warning notification.
- * This method assumes that the message ID has already been checked for CMAS type.
- * @return the CMAS message class as defined in {@link SmsCbCmasInfo}
- */
- private int getCmasMessageClass() {
- switch (mMessageIdentifier) {
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL_LANGUAGE:
- return SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT;
-
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE:
- return SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT;
-
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE:
- return SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT;
-
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY_LANGUAGE:
- return SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY;
-
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST_LANGUAGE:
- return SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST;
-
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXERCISE:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXERCISE_LANGUAGE:
- return SmsCbCmasInfo.CMAS_CLASS_CMAS_EXERCISE;
-
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE_LANGUAGE:
- return SmsCbCmasInfo.CMAS_CLASS_OPERATOR_DEFINED_USE;
-
- default:
- return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN;
- }
- }
-
- /**
- * Returns the severity for a CMAS warning notification. This is only available for extreme
- * and severe alerts, not for other types such as Presidential Level and AMBER alerts.
- * This method assumes that the message ID has already been checked for CMAS type.
- * @return the CMAS severity as defined in {@link SmsCbCmasInfo}
- */
- private int getCmasSeverity() {
- switch (mMessageIdentifier) {
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE:
- return SmsCbCmasInfo.CMAS_SEVERITY_EXTREME;
-
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE:
- return SmsCbCmasInfo.CMAS_SEVERITY_SEVERE;
-
- default:
- return SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN;
- }
- }
-
- /**
- * Returns the urgency for a CMAS warning notification. This is only available for extreme
- * and severe alerts, not for other types such as Presidential Level and AMBER alerts.
- * This method assumes that the message ID has already been checked for CMAS type.
- * @return the CMAS urgency as defined in {@link SmsCbCmasInfo}
- */
- private int getCmasUrgency() {
- switch (mMessageIdentifier) {
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE:
- return SmsCbCmasInfo.CMAS_URGENCY_IMMEDIATE;
-
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE:
- return SmsCbCmasInfo.CMAS_URGENCY_EXPECTED;
-
- default:
- return SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN;
- }
- }
-
- /**
- * Returns the certainty for a CMAS warning notification. This is only available for extreme
- * and severe alerts, not for other types such as Presidential Level and AMBER alerts.
- * This method assumes that the message ID has already been checked for CMAS type.
- * @return the CMAS certainty as defined in {@link SmsCbCmasInfo}
- */
- private int getCmasCertainty() {
- switch (mMessageIdentifier) {
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE:
- return SmsCbCmasInfo.CMAS_CERTAINTY_OBSERVED;
-
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
- case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE:
- return SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY;
-
- default:
- return SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN;
- }
- }
-
- @Override
- public String toString() {
- return "SmsCbHeader{GS=" + mGeographicalScope + ", serialNumber=0x"
- + Integer.toHexString(mSerialNumber)
- + ", messageIdentifier=0x" + Integer.toHexString(mMessageIdentifier)
- + ", format=" + mFormat
- + ", DCS=0x" + Integer.toHexString(mDataCodingScheme)
- + ", page " + mPageIndex + " of " + mNrOfPages + '}';
- }
-
- /**
- * CBS Data Coding Scheme.
- * Reference: 3GPP TS 23.038 version 15.0.0 section #5, CBS Data Coding Scheme
- */
- public static final class DataCodingScheme {
- public final int encoding;
- public final String language;
- public final boolean hasLanguageIndicator;
-
- public DataCodingScheme(int dataCodingScheme) {
- int encoding = 0;
- String language = null;
- boolean hasLanguageIndicator = false;
-
- // Extract encoding and language from DCS, as defined in 3gpp TS 23.038,
- // section 5.
- switch ((dataCodingScheme & 0xf0) >> 4) {
- case 0x00:
- encoding = SmsConstants.ENCODING_7BIT;
- language = LANGUAGE_CODES_GROUP_0[dataCodingScheme & 0x0f];
- break;
-
- case 0x01:
- hasLanguageIndicator = true;
- if ((dataCodingScheme & 0x0f) == 0x01) {
- encoding = SmsConstants.ENCODING_16BIT;
- } else {
- encoding = SmsConstants.ENCODING_7BIT;
- }
- break;
-
- case 0x02:
- encoding = SmsConstants.ENCODING_7BIT;
- language = LANGUAGE_CODES_GROUP_2[dataCodingScheme & 0x0f];
- break;
-
- case 0x03:
- encoding = SmsConstants.ENCODING_7BIT;
- break;
-
- case 0x04:
- case 0x05:
- switch ((dataCodingScheme & 0x0c) >> 2) {
- case 0x01:
- encoding = SmsConstants.ENCODING_8BIT;
- break;
-
- case 0x02:
- encoding = SmsConstants.ENCODING_16BIT;
- break;
-
- case 0x00:
- default:
- encoding = SmsConstants.ENCODING_7BIT;
- break;
- }
- break;
-
- case 0x06:
- case 0x07:
- // Compression not supported
- case 0x09:
- // UDH structure not supported
- case 0x0e:
- // Defined by the WAP forum not supported
- throw new IllegalArgumentException("Unsupported GSM dataCodingScheme "
- + dataCodingScheme);
-
- case 0x0f:
- if (((dataCodingScheme & 0x04) >> 2) == 0x01) {
- encoding = SmsConstants.ENCODING_8BIT;
- } else {
- encoding = SmsConstants.ENCODING_7BIT;
- }
- break;
-
- default:
- // Reserved values are to be treated as 7-bit
- encoding = SmsConstants.ENCODING_7BIT;
- break;
- }
-
-
- this.encoding = encoding;
- this.language = language;
- this.hasLanguageIndicator = hasLanguageIndicator;
- }
- }
-}
diff --git a/src/com/android/cellbroadcastreceiver/State.java b/src/com/android/cellbroadcastreceiver/State.java
deleted file mode 100644
index 32e0db07a..000000000
--- a/src/com/android/cellbroadcastreceiver/State.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/**
- * 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.
- */
-
-package com.android.cellbroadcastreceiver;
-
-import android.annotation.UnsupportedAppUsage;
-import android.os.Message;
-
-/**
- * {@hide}
- *
- * The class for implementing states in a StateMachine
- */
-public class State implements IState {
-
- /**
- * Constructor
- */
- @UnsupportedAppUsage
- protected State() {
- }
-
- /* (non-Javadoc)
- * @see com.android.internal.util.IState#enter()
- */
- @UnsupportedAppUsage
- @Override
- public void enter() {
- }
-
- /* (non-Javadoc)
- * @see com.android.internal.util.IState#exit()
- */
- @UnsupportedAppUsage
- @Override
- public void exit() {
- }
-
- /* (non-Javadoc)
- * @see com.android.internal.util.IState#processMessage(android.os.Message)
- */
- @UnsupportedAppUsage
- @Override
- public boolean processMessage(Message msg) {
- return false;
- }
-
- /**
- * Name of State for debugging purposes.
- *
- * This default implementation returns the class name, returning
- * the instance name would better in cases where a State class
- * is used for multiple states. But normally there is one class per
- * state and the class name is sufficient and easy to get. You may
- * want to provide a setName or some other mechanism for setting
- * another name if the class name is not appropriate.
- *
- * @see com.android.internal.util.IState#processMessage(android.os.Message)
- */
- @UnsupportedAppUsage
- @Override
- public String getName() {
- String name = getClass().getName();
- int lastDollar = name.lastIndexOf('$');
- return name.substring(lastDollar + 1);
- }
-}
diff --git a/src/com/android/cellbroadcastreceiver/StateMachine.java b/src/com/android/cellbroadcastreceiver/StateMachine.java
deleted file mode 100644
index 2a2fb43b4..000000000
--- a/src/com/android/cellbroadcastreceiver/StateMachine.java
+++ /dev/null
@@ -1,2183 +0,0 @@
-/**
- * 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.
- */
-
-package com.android.cellbroadcastreceiver;
-
-import android.annotation.UnsupportedAppUsage;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Vector;
-
-/**
- * {@hide}
- *
- * <p>The state machine defined here is a hierarchical state machine which processes messages
- * and can have states arranged hierarchically.</p>
- *
- * <p>A state is a <code>State</code> object and must implement
- * <code>processMessage</code> and optionally <code>enter/exit/getName</code>.
- * The enter/exit methods are equivalent to the construction and destruction
- * in Object Oriented programming and are used to perform initialization and
- * cleanup of the state respectively. The <code>getName</code> method returns the
- * name of the state; the default implementation returns the class name. It may be
- * desirable to have <code>getName</code> return the the state instance name instead,
- * in particular if a particular state class has multiple instances.</p>
- *
- * <p>When a state machine is created, <code>addState</code> is used to build the
- * hierarchy and <code>setInitialState</code> is used to identify which of these
- * is the initial state. After construction the programmer calls <code>start</code>
- * which initializes and starts the state machine. The first action the StateMachine
- * is to the invoke <code>enter</code> for all of the initial state's hierarchy,
- * starting at its eldest parent. The calls to enter will be done in the context
- * of the StateMachine's Handler, not in the context of the call to start, and they
- * will be invoked before any messages are processed. For example, given the simple
- * state machine below, mP1.enter will be invoked and then mS1.enter. Finally,
- * messages sent to the state machine will be processed by the current state;
- * in our simple state machine below that would initially be mS1.processMessage.</p>
-<pre>
- mP1
- / \
- mS2 mS1 ----&gt; initial state
-</pre>
- * <p>After the state machine is created and started, messages are sent to a state
- * machine using <code>sendMessage</code> and the messages are created using
- * <code>obtainMessage</code>. When the state machine receives a message the
- * current state's <code>processMessage</code> is invoked. In the above example
- * mS1.processMessage will be invoked first. The state may use <code>transitionTo</code>
- * to change the current state to a new state.</p>
- *
- * <p>Each state in the state machine may have a zero or one parent states. If
- * a child state is unable to handle a message it may have the message processed
- * by its parent by returning false or NOT_HANDLED. If a message is not handled by
- * a child state or any of its ancestors, <code>unhandledMessage</code> will be invoked
- * to give one last chance for the state machine to process the message.</p>
- *
- * <p>When all processing is completed a state machine may choose to call
- * <code>transitionToHaltingState</code>. When the current <code>processingMessage</code>
- * returns the state machine will transfer to an internal <code>HaltingState</code>
- * and invoke <code>halting</code>. Any message subsequently received by the state
- * machine will cause <code>haltedProcessMessage</code> to be invoked.</p>
- *
- * <p>If it is desirable to completely stop the state machine call <code>quit</code> or
- * <code>quitNow</code>. These will call <code>exit</code> of the current state and its parents,
- * call <code>onQuitting</code> and then exit Thread/Loopers.</p>
- *
- * <p>In addition to <code>processMessage</code> each <code>State</code> has
- * an <code>enter</code> method and <code>exit</code> method which may be overridden.</p>
- *
- * <p>Since the states are arranged in a hierarchy transitioning to a new state
- * causes current states to be exited and new states to be entered. To determine
- * the list of states to be entered/exited the common parent closest to
- * the current state is found. We then exit from the current state and its
- * parent's up to but not including the common parent state and then enter all
- * of the new states below the common parent down to the destination state.
- * If there is no common parent all states are exited and then the new states
- * are entered.</p>
- *
- * <p>Two other methods that states can use are <code>deferMessage</code> and
- * <code>sendMessageAtFrontOfQueue</code>. The <code>sendMessageAtFrontOfQueue</code> sends
- * a message but places it on the front of the queue rather than the back. The
- * <code>deferMessage</code> causes the message to be saved on a list until a
- * transition is made to a new state. At which time all of the deferred messages
- * will be put on the front of the state machine queue with the oldest message
- * at the front. These will then be processed by the new current state before
- * any other messages that are on the queue or might be added later. Both of
- * these are protected and may only be invoked from within a state machine.</p>
- *
- * <p>To illustrate some of these properties we'll use state machine with an 8
- * state hierarchy:</p>
-<pre>
- mP0
- / \
- mP1 mS0
- / \
- mS2 mS1
- / \ \
- mS3 mS4 mS5 ---&gt; initial state
-</pre>
- * <p>After starting mS5 the list of active states is mP0, mP1, mS1 and mS5.
- * So the order of calling processMessage when a message is received is mS5,
- * mS1, mP1, mP0 assuming each processMessage indicates it can't handle this
- * message by returning false or NOT_HANDLED.</p>
- *
- * <p>Now assume mS5.processMessage receives a message it can handle, and during
- * the handling determines the machine should change states. It could call
- * transitionTo(mS4) and return true or HANDLED. Immediately after returning from
- * processMessage the state machine runtime will find the common parent,
- * which is mP1. It will then call mS5.exit, mS1.exit, mS2.enter and then
- * mS4.enter. The new list of active states is mP0, mP1, mS2 and mS4. So
- * when the next message is received mS4.processMessage will be invoked.</p>
- *
- * <p>Now for some concrete examples, here is the canonical HelloWorld as a state machine.
- * It responds with "Hello World" being printed to the log for every message.</p>
-<pre>
-class HelloWorld extends StateMachine {
- HelloWorld(String name) {
- super(name);
- addState(mState1);
- setInitialState(mState1);
- }
-
- public static HelloWorld makeHelloWorld() {
- HelloWorld hw = new HelloWorld("hw");
- hw.start();
- return hw;
- }
-
- class State1 extends State {
- &#64;Override public boolean processMessage(Message message) {
- log("Hello World");
- return HANDLED;
- }
- }
- State1 mState1 = new State1();
-}
-
-void testHelloWorld() {
- HelloWorld hw = makeHelloWorld();
- hw.sendMessage(hw.obtainMessage());
-}
-</pre>
- * <p>A more interesting state machine is one with four states
- * with two independent parent states.</p>
-<pre>
- mP1 mP2
- / \
- mS2 mS1
-</pre>
- * <p>Here is a description of this state machine using pseudo code.</p>
- <pre>
-state mP1 {
- enter { log("mP1.enter"); }
- exit { log("mP1.exit"); }
- on msg {
- CMD_2 {
- send(CMD_3);
- defer(msg);
- transitionTo(mS2);
- return HANDLED;
- }
- return NOT_HANDLED;
- }
-}
-
-INITIAL
-state mS1 parent mP1 {
- enter { log("mS1.enter"); }
- exit { log("mS1.exit"); }
- on msg {
- CMD_1 {
- transitionTo(mS1);
- return HANDLED;
- }
- return NOT_HANDLED;
- }
-}
-
-state mS2 parent mP1 {
- enter { log("mS2.enter"); }
- exit { log("mS2.exit"); }
- on msg {
- CMD_2 {
- send(CMD_4);
- return HANDLED;
- }
- CMD_3 {
- defer(msg);
- transitionTo(mP2);
- return HANDLED;
- }
- return NOT_HANDLED;
- }
-}
-
-state mP2 {
- enter {
- log("mP2.enter");
- send(CMD_5);
- }
- exit { log("mP2.exit"); }
- on msg {
- CMD_3, CMD_4 { return HANDLED; }
- CMD_5 {
- transitionTo(HaltingState);
- return HANDLED;
- }
- return NOT_HANDLED;
- }
-}
-</pre>
- * <p>The implementation is below and also in StateMachineTest:</p>
-<pre>
-class Hsm1 extends StateMachine {
- public static final int CMD_1 = 1;
- public static final int CMD_2 = 2;
- public static final int CMD_3 = 3;
- public static final int CMD_4 = 4;
- public static final int CMD_5 = 5;
-
- public static Hsm1 makeHsm1() {
- log("makeHsm1 E");
- Hsm1 sm = new Hsm1("hsm1");
- sm.start();
- log("makeHsm1 X");
- return sm;
- }
-
- Hsm1(String name) {
- super(name);
- log("ctor E");
-
- // Add states, use indentation to show hierarchy
- addState(mP1);
- addState(mS1, mP1);
- addState(mS2, mP1);
- addState(mP2);
-
- // Set the initial state
- setInitialState(mS1);
- log("ctor X");
- }
-
- class P1 extends State {
- &#64;Override public void enter() {
- log("mP1.enter");
- }
- &#64;Override public boolean processMessage(Message message) {
- boolean retVal;
- log("mP1.processMessage what=" + message.what);
- switch(message.what) {
- case CMD_2:
- // CMD_2 will arrive in mS2 before CMD_3
- sendMessage(obtainMessage(CMD_3));
- deferMessage(message);
- transitionTo(mS2);
- retVal = HANDLED;
- break;
- default:
- // Any message we don't understand in this state invokes unhandledMessage
- retVal = NOT_HANDLED;
- break;
- }
- return retVal;
- }
- &#64;Override public void exit() {
- log("mP1.exit");
- }
- }
-
- class S1 extends State {
- &#64;Override public void enter() {
- log("mS1.enter");
- }
- &#64;Override public boolean processMessage(Message message) {
- log("S1.processMessage what=" + message.what);
- if (message.what == CMD_1) {
- // Transition to ourself to show that enter/exit is called
- transitionTo(mS1);
- return HANDLED;
- } else {
- // Let parent process all other messages
- return NOT_HANDLED;
- }
- }
- &#64;Override public void exit() {
- log("mS1.exit");
- }
- }
-
- class S2 extends State {
- &#64;Override public void enter() {
- log("mS2.enter");
- }
- &#64;Override public boolean processMessage(Message message) {
- boolean retVal;
- log("mS2.processMessage what=" + message.what);
- switch(message.what) {
- case(CMD_2):
- sendMessage(obtainMessage(CMD_4));
- retVal = HANDLED;
- break;
- case(CMD_3):
- deferMessage(message);
- transitionTo(mP2);
- retVal = HANDLED;
- break;
- default:
- retVal = NOT_HANDLED;
- break;
- }
- return retVal;
- }
- &#64;Override public void exit() {
- log("mS2.exit");
- }
- }
-
- class P2 extends State {
- &#64;Override public void enter() {
- log("mP2.enter");
- sendMessage(obtainMessage(CMD_5));
- }
- &#64;Override public boolean processMessage(Message message) {
- log("P2.processMessage what=" + message.what);
- switch(message.what) {
- case(CMD_3):
- break;
- case(CMD_4):
- break;
- case(CMD_5):
- transitionToHaltingState();
- break;
- }
- return HANDLED;
- }
- &#64;Override public void exit() {
- log("mP2.exit");
- }
- }
-
- &#64;Override
- void onHalting() {
- log("halting");
- synchronized (this) {
- this.notifyAll();
- }
- }
-
- P1 mP1 = new P1();
- S1 mS1 = new S1();
- S2 mS2 = new S2();
- P2 mP2 = new P2();
-}
-</pre>
- * <p>If this is executed by sending two messages CMD_1 and CMD_2
- * (Note the synchronize is only needed because we use hsm.wait())</p>
-<pre>
-Hsm1 hsm = makeHsm1();
-synchronize(hsm) {
- hsm.sendMessage(obtainMessage(hsm.CMD_1));
- hsm.sendMessage(obtainMessage(hsm.CMD_2));
- try {
- // wait for the messages to be handled
- hsm.wait();
- } catch (InterruptedException e) {
- loge("exception while waiting " + e.getMessage());
- }
-}
-</pre>
- * <p>The output is:</p>
-<pre>
-D/hsm1 ( 1999): makeHsm1 E
-D/hsm1 ( 1999): ctor E
-D/hsm1 ( 1999): ctor X
-D/hsm1 ( 1999): mP1.enter
-D/hsm1 ( 1999): mS1.enter
-D/hsm1 ( 1999): makeHsm1 X
-D/hsm1 ( 1999): mS1.processMessage what=1
-D/hsm1 ( 1999): mS1.exit
-D/hsm1 ( 1999): mS1.enter
-D/hsm1 ( 1999): mS1.processMessage what=2
-D/hsm1 ( 1999): mP1.processMessage what=2
-D/hsm1 ( 1999): mS1.exit
-D/hsm1 ( 1999): mS2.enter
-D/hsm1 ( 1999): mS2.processMessage what=2
-D/hsm1 ( 1999): mS2.processMessage what=3
-D/hsm1 ( 1999): mS2.exit
-D/hsm1 ( 1999): mP1.exit
-D/hsm1 ( 1999): mP2.enter
-D/hsm1 ( 1999): mP2.processMessage what=3
-D/hsm1 ( 1999): mP2.processMessage what=4
-D/hsm1 ( 1999): mP2.processMessage what=5
-D/hsm1 ( 1999): mP2.exit
-D/hsm1 ( 1999): halting
-</pre>
- */
-public class StateMachine {
- // Name of the state machine and used as logging tag
- private String mName;
-
- /** Message.what value when quitting */
- private static final int SM_QUIT_CMD = -1;
-
- /** Message.what value when initializing */
- private static final int SM_INIT_CMD = -2;
-
- /**
- * Convenience constant that maybe returned by processMessage
- * to indicate the the message was processed and is not to be
- * processed by parent states
- */
- public static final boolean HANDLED = true;
-
- /**
- * Convenience constant that maybe returned by processMessage
- * to indicate the the message was NOT processed and is to be
- * processed by parent states
- */
- public static final boolean NOT_HANDLED = false;
-
- /**
- * StateMachine logging record.
- * {@hide}
- */
- public static class LogRec {
- private StateMachine mSm;
- private long mTime;
- private int mWhat;
- private String mInfo;
- private IState mState;
- private IState mOrgState;
- private IState mDstState;
-
- /**
- * Constructor
- *
- * @param msg
- * @param state the state which handled the message
- * @param orgState is the first state the received the message but
- * did not processes the message.
- * @param transToState is the state that was transitioned to after the message was
- * processed.
- */
- LogRec(StateMachine sm, Message msg, String info, IState state, IState orgState,
- IState transToState) {
- update(sm, msg, info, state, orgState, transToState);
- }
-
- /**
- * Update the information in the record.
- * @param state that handled the message
- * @param orgState is the first state the received the message
- * @param dstState is the state that was the transition target when logging
- */
- public void update(StateMachine sm, Message msg, String info, IState state, IState orgState,
- IState dstState) {
- mSm = sm;
- mTime = System.currentTimeMillis();
- mWhat = (msg != null) ? msg.what : 0;
- mInfo = info;
- mState = state;
- mOrgState = orgState;
- mDstState = dstState;
- }
-
- /**
- * @return time stamp
- */
- public long getTime() {
- return mTime;
- }
-
- /**
- * @return msg.what
- */
- public long getWhat() {
- return mWhat;
- }
-
- /**
- * @return the command that was executing
- */
- public String getInfo() {
- return mInfo;
- }
-
- /**
- * @return the state that handled this message
- */
- public IState getState() {
- return mState;
- }
-
- /**
- * @return the state destination state if a transition is occurring or null if none.
- */
- public IState getDestState() {
- return mDstState;
- }
-
- /**
- * @return the original state that received the message.
- */
- public IState getOriginalState() {
- return mOrgState;
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append("time=");
- Calendar c = Calendar.getInstance();
- c.setTimeInMillis(mTime);
- sb.append(String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c));
- sb.append(" processed=");
- sb.append(mState == null ? "<null>" : mState.getName());
- sb.append(" org=");
- sb.append(mOrgState == null ? "<null>" : mOrgState.getName());
- sb.append(" dest=");
- sb.append(mDstState == null ? "<null>" : mDstState.getName());
- sb.append(" what=");
- String what = mSm != null ? mSm.getWhatToString(mWhat) : "";
- if (TextUtils.isEmpty(what)) {
- sb.append(mWhat);
- sb.append("(0x");
- sb.append(Integer.toHexString(mWhat));
- sb.append(")");
- } else {
- sb.append(what);
- }
- if (!TextUtils.isEmpty(mInfo)) {
- sb.append(" ");
- sb.append(mInfo);
- }
- return sb.toString();
- }
- }
-
- /**
- * A list of log records including messages recently processed by the state machine.
- *
- * The class maintains a list of log records including messages
- * recently processed. The list is finite and may be set in the
- * constructor or by calling setSize. The public interface also
- * includes size which returns the number of recent records,
- * count which is the number of records processed since the
- * the last setSize, get which returns a record and
- * add which adds a record.
- */
- private static class LogRecords {
-
- private static final int DEFAULT_SIZE = 20;
-
- private Vector<LogRec> mLogRecVector = new Vector<LogRec>();
- private int mMaxSize = DEFAULT_SIZE;
- private int mOldestIndex = 0;
- private int mCount = 0;
- private boolean mLogOnlyTransitions = false;
-
- /**
- * private constructor use add
- */
- private LogRecords() {
- }
-
- /**
- * Set size of messages to maintain and clears all current records.
- *
- * @param maxSize number of records to maintain at anyone time.
- */
- synchronized void setSize(int maxSize) {
- // TODO: once b/28217358 is fixed, add unit tests to verify that these variables are
- // cleared after calling this method, and that subsequent calls to get() function as
- // expected.
- mMaxSize = maxSize;
- mOldestIndex = 0;
- mCount = 0;
- mLogRecVector.clear();
- }
-
- synchronized void setLogOnlyTransitions(boolean enable) {
- mLogOnlyTransitions = enable;
- }
-
- synchronized boolean logOnlyTransitions() {
- return mLogOnlyTransitions;
- }
-
- /**
- * @return the number of recent records.
- */
- synchronized int size() {
- return mLogRecVector.size();
- }
-
- /**
- * @return the total number of records processed since size was set.
- */
- synchronized int count() {
- return mCount;
- }
-
- /**
- * Clear the list of records.
- */
- synchronized void cleanup() {
- mLogRecVector.clear();
- }
-
- /**
- * @return the information on a particular record. 0 is the oldest
- * record and size()-1 is the newest record. If the index is to
- * large null is returned.
- */
- synchronized LogRec get(int index) {
- int nextIndex = mOldestIndex + index;
- if (nextIndex >= mMaxSize) {
- nextIndex -= mMaxSize;
- }
- if (nextIndex >= size()) {
- return null;
- } else {
- return mLogRecVector.get(nextIndex);
- }
- }
-
- /**
- * Add a processed message.
- *
- * @param msg
- * @param messageInfo to be stored
- * @param state that handled the message
- * @param orgState is the first state the received the message but
- * did not processes the message.
- * @param transToState is the state that was transitioned to after the message was
- * processed.
- *
- */
- synchronized void add(StateMachine sm, Message msg, String messageInfo, IState state,
- IState orgState, IState transToState) {
- mCount += 1;
- if (mLogRecVector.size() < mMaxSize) {
- mLogRecVector.add(new LogRec(sm, msg, messageInfo, state, orgState, transToState));
- } else {
- LogRec pmi = mLogRecVector.get(mOldestIndex);
- mOldestIndex += 1;
- if (mOldestIndex >= mMaxSize) {
- mOldestIndex = 0;
- }
- pmi.update(sm, msg, messageInfo, state, orgState, transToState);
- }
- }
- }
-
- private static class SmHandler extends Handler {
-
- /** true if StateMachine has quit */
- private boolean mHasQuit = false;
-
- /** The debug flag */
- private boolean mDbg = false;
-
- /** The SmHandler object, identifies that message is internal */
- private static final Object sSmHandlerObj = new Object();
-
- /** The current message */
- private Message mMsg;
-
- /** A list of log records including messages this state machine has processed */
- private LogRecords mLogRecords = new LogRecords();
-
- /** true if construction of the state machine has not been completed */
- private boolean mIsConstructionCompleted;
-
- /** Stack used to manage the current hierarchy of states */
- private StateInfo[] mStateStack;
-
- /** Top of mStateStack */
- private int mStateStackTopIndex = -1;
-
- /** A temporary stack used to manage the state stack */
- private StateInfo[] mTempStateStack;
-
- /** The top of the mTempStateStack */
- private int mTempStateStackCount;
-
- /** State used when state machine is halted */
- private HaltingState mHaltingState = new HaltingState();
-
- /** State used when state machine is quitting */
- private QuittingState mQuittingState = new QuittingState();
-
- /** Reference to the StateMachine */
- private StateMachine mSm;
-
- /**
- * Information about a state.
- * Used to maintain the hierarchy.
- */
- private class StateInfo {
- /** The state */
- State state;
-
- /** The parent of this state, null if there is no parent */
- StateInfo parentStateInfo;
-
- /** True when the state has been entered and on the stack */
- boolean active;
-
- /**
- * Convert StateInfo to string
- */
- @Override
- public String toString() {
- return "state=" + state.getName() + ",active=" + active + ",parent="
- + ((parentStateInfo == null) ? "null" : parentStateInfo.state.getName());
- }
- }
-
- /** The map of all of the states in the state machine */
- private HashMap<State, StateInfo> mStateInfo = new HashMap<State, StateInfo>();
-
- /** The initial state that will process the first message */
- private State mInitialState;
-
- /** The destination state when transitionTo has been invoked */
- private State mDestState;
-
- /**
- * Indicates if a transition is in progress
- *
- * This will be true for all calls of State.exit and all calls of State.enter except for the
- * last enter call for the current destination state.
- */
- private boolean mTransitionInProgress = false;
-
- /** The list of deferred messages */
- private ArrayList<Message> mDeferredMessages = new ArrayList<Message>();
-
- /**
- * State entered when transitionToHaltingState is called.
- */
- private class HaltingState extends State {
- @Override
- public boolean processMessage(Message msg) {
- mSm.haltedProcessMessage(msg);
- return true;
- }
- }
-
- /**
- * State entered when a valid quit message is handled.
- */
- private class QuittingState extends State {
- @Override
- public boolean processMessage(Message msg) {
- return NOT_HANDLED;
- }
- }
-
- /**
- * Handle messages sent to the state machine by calling
- * the current state's processMessage. It also handles
- * the enter/exit calls and placing any deferred messages
- * back onto the queue when transitioning to a new state.
- */
- @Override
- public final void handleMessage(Message msg) {
- if (!mHasQuit) {
- if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) {
- mSm.onPreHandleMessage(msg);
- }
-
- if (mDbg) mSm.log("handleMessage: E msg.what=" + msg.what);
-
- /** Save the current message */
- mMsg = msg;
-
- /** State that processed the message */
- State msgProcessedState = null;
- if (mIsConstructionCompleted || (mMsg.what == SM_QUIT_CMD)) {
- /** Normal path */
- msgProcessedState = processMsg(msg);
- } else if (!mIsConstructionCompleted && (mMsg.what == SM_INIT_CMD)
- && (mMsg.obj == sSmHandlerObj)) {
- /** Initial one time path. */
- mIsConstructionCompleted = true;
- invokeEnterMethods(0);
- } else {
- throw new RuntimeException("StateMachine.handleMessage: "
- + "The start method not called, received msg: " + msg);
- }
- performTransitions(msgProcessedState, msg);
-
- // We need to check if mSm == null here as we could be quitting.
- if (mDbg && mSm != null) mSm.log("handleMessage: X");
-
- if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) {
- mSm.onPostHandleMessage(msg);
- }
- }
- }
-
- /**
- * Do any transitions
- * @param msgProcessedState is the state that processed the message
- */
- private void performTransitions(State msgProcessedState, Message msg) {
- /**
- * If transitionTo has been called, exit and then enter
- * the appropriate states. We loop on this to allow
- * enter and exit methods to use transitionTo.
- */
- State orgState = mStateStack[mStateStackTopIndex].state;
-
- /**
- * Record whether message needs to be logged before we transition and
- * and we won't log special messages SM_INIT_CMD or SM_QUIT_CMD which
- * always set msg.obj to the handler.
- */
- boolean recordLogMsg = mSm.recordLogRec(mMsg) && (msg.obj != sSmHandlerObj);
-
- if (mLogRecords.logOnlyTransitions()) {
- /** Record only if there is a transition */
- if (mDestState != null) {
- mLogRecords.add(mSm, mMsg, mSm.getLogRecString(mMsg), msgProcessedState,
- orgState, mDestState);
- }
- } else if (recordLogMsg) {
- /** Record message */
- mLogRecords.add(mSm, mMsg, mSm.getLogRecString(mMsg), msgProcessedState, orgState,
- mDestState);
- }
-
- State destState = mDestState;
- if (destState != null) {
- /**
- * Process the transitions including transitions in the enter/exit methods
- */
- while (true) {
- if (mDbg) mSm.log("handleMessage: new destination call exit/enter");
-
- /**
- * Determine the states to exit and enter and return the
- * common ancestor state of the enter/exit states. Then
- * invoke the exit methods then the enter methods.
- */
- StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);
- // flag is cleared in invokeEnterMethods before entering the target state
- mTransitionInProgress = true;
- invokeExitMethods(commonStateInfo);
- int stateStackEnteringIndex = moveTempStateStackToStateStack();
- invokeEnterMethods(stateStackEnteringIndex);
-
- /**
- * Since we have transitioned to a new state we need to have
- * any deferred messages moved to the front of the message queue
- * so they will be processed before any other messages in the
- * message queue.
- */
- moveDeferredMessageAtFrontOfQueue();
-
- if (destState != mDestState) {
- // A new mDestState so continue looping
- destState = mDestState;
- } else {
- // No change in mDestState so we're done
- break;
- }
- }
- mDestState = null;
- }
-
- /**
- * After processing all transitions check and
- * see if the last transition was to quit or halt.
- */
- if (destState != null) {
- if (destState == mQuittingState) {
- /**
- * Call onQuitting to let subclasses cleanup.
- */
- mSm.onQuitting();
- cleanupAfterQuitting();
- } else if (destState == mHaltingState) {
- /**
- * Call onHalting() if we've transitioned to the halting
- * state. All subsequent messages will be processed in
- * in the halting state which invokes haltedProcessMessage(msg);
- */
- mSm.onHalting();
- }
- }
- }
-
- /**
- * Cleanup all the static variables and the looper after the SM has been quit.
- */
- private final void cleanupAfterQuitting() {
- if (mSm.mSmThread != null) {
- // If we made the thread then quit looper which stops the thread.
- getLooper().quit();
- mSm.mSmThread = null;
- }
-
- mSm.mSmHandler = null;
- mSm = null;
- mMsg = null;
- mLogRecords.cleanup();
- mStateStack = null;
- mTempStateStack = null;
- mStateInfo.clear();
- mInitialState = null;
- mDestState = null;
- mDeferredMessages.clear();
- mHasQuit = true;
- }
-
- /**
- * Complete the construction of the state machine.
- */
- private final void completeConstruction() {
- if (mDbg) mSm.log("completeConstruction: E");
-
- /**
- * Determine the maximum depth of the state hierarchy
- * so we can allocate the state stacks.
- */
- int maxDepth = 0;
- for (StateInfo si : mStateInfo.values()) {
- int depth = 0;
- for (StateInfo i = si; i != null; depth++) {
- i = i.parentStateInfo;
- }
- if (maxDepth < depth) {
- maxDepth = depth;
- }
- }
- if (mDbg) mSm.log("completeConstruction: maxDepth=" + maxDepth);
-
- mStateStack = new StateInfo[maxDepth];
- mTempStateStack = new StateInfo[maxDepth];
- setupInitialStateStack();
-
- /** Sending SM_INIT_CMD message to invoke enter methods asynchronously */
- sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, sSmHandlerObj));
-
- if (mDbg) mSm.log("completeConstruction: X");
- }
-
- /**
- * Process the message. If the current state doesn't handle
- * it, call the states parent and so on. If it is never handled then
- * call the state machines unhandledMessage method.
- * @return the state that processed the message
- */
- private final State processMsg(Message msg) {
- StateInfo curStateInfo = mStateStack[mStateStackTopIndex];
- if (mDbg) {
- mSm.log("processMsg: " + curStateInfo.state.getName());
- }
-
- if (isQuit(msg)) {
- transitionTo(mQuittingState);
- } else {
- while (!curStateInfo.state.processMessage(msg)) {
- /**
- * Not processed
- */
- curStateInfo = curStateInfo.parentStateInfo;
- if (curStateInfo == null) {
- /**
- * No parents left so it's not handled
- */
- mSm.unhandledMessage(msg);
- break;
- }
- if (mDbg) {
- mSm.log("processMsg: " + curStateInfo.state.getName());
- }
- }
- }
- return (curStateInfo != null) ? curStateInfo.state : null;
- }
-
- /**
- * Call the exit method for each state from the top of stack
- * up to the common ancestor state.
- */
- private final void invokeExitMethods(StateInfo commonStateInfo) {
- while ((mStateStackTopIndex >= 0)
- && (mStateStack[mStateStackTopIndex] != commonStateInfo)) {
- State curState = mStateStack[mStateStackTopIndex].state;
- if (mDbg) mSm.log("invokeExitMethods: " + curState.getName());
- curState.exit();
- mStateStack[mStateStackTopIndex].active = false;
- mStateStackTopIndex -= 1;
- }
- }
-
- /**
- * Invoke the enter method starting at the entering index to top of state stack
- */
- private final void invokeEnterMethods(int stateStackEnteringIndex) {
- for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) {
- if (stateStackEnteringIndex == mStateStackTopIndex) {
- // Last enter state for transition
- mTransitionInProgress = false;
- }
- if (mDbg) mSm.log("invokeEnterMethods: " + mStateStack[i].state.getName());
- mStateStack[i].state.enter();
- mStateStack[i].active = true;
- }
- mTransitionInProgress = false; // ensure flag set to false if no methods called
- }
-
- /**
- * Move the deferred message to the front of the message queue.
- */
- private final void moveDeferredMessageAtFrontOfQueue() {
- /**
- * The oldest messages on the deferred list must be at
- * the front of the queue so start at the back, which
- * as the most resent message and end with the oldest
- * messages at the front of the queue.
- */
- for (int i = mDeferredMessages.size() - 1; i >= 0; i--) {
- Message curMsg = mDeferredMessages.get(i);
- if (mDbg) mSm.log("moveDeferredMessageAtFrontOfQueue; what=" + curMsg.what);
- sendMessageAtFrontOfQueue(curMsg);
- }
- mDeferredMessages.clear();
- }
-
- /**
- * Move the contents of the temporary stack to the state stack
- * reversing the order of the items on the temporary stack as
- * they are moved.
- *
- * @return index into mStateStack where entering needs to start
- */
- private final int moveTempStateStackToStateStack() {
- int startingIndex = mStateStackTopIndex + 1;
- int i = mTempStateStackCount - 1;
- int j = startingIndex;
- while (i >= 0) {
- if (mDbg) mSm.log("moveTempStackToStateStack: i=" + i + ",j=" + j);
- mStateStack[j] = mTempStateStack[i];
- j += 1;
- i -= 1;
- }
-
- mStateStackTopIndex = j - 1;
- if (mDbg) {
- mSm.log("moveTempStackToStateStack: X mStateStackTop=" + mStateStackTopIndex
- + ",startingIndex=" + startingIndex + ",Top="
- + mStateStack[mStateStackTopIndex].state.getName());
- }
- return startingIndex;
- }
-
- /**
- * Setup the mTempStateStack with the states we are going to enter.
- *
- * This is found by searching up the destState's ancestors for a
- * state that is already active i.e. StateInfo.active == true.
- * The destStae and all of its inactive parents will be on the
- * TempStateStack as the list of states to enter.
- *
- * @return StateInfo of the common ancestor for the destState and
- * current state or null if there is no common parent.
- */
- private final StateInfo setupTempStateStackWithStatesToEnter(State destState) {
- /**
- * Search up the parent list of the destination state for an active
- * state. Use a do while() loop as the destState must always be entered
- * even if it is active. This can happen if we are exiting/entering
- * the current state.
- */
- mTempStateStackCount = 0;
- StateInfo curStateInfo = mStateInfo.get(destState);
- do {
- mTempStateStack[mTempStateStackCount++] = curStateInfo;
- curStateInfo = curStateInfo.parentStateInfo;
- } while ((curStateInfo != null) && !curStateInfo.active);
-
- if (mDbg) {
- mSm.log("setupTempStateStackWithStatesToEnter: X mTempStateStackCount="
- + mTempStateStackCount + ",curStateInfo: " + curStateInfo);
- }
- return curStateInfo;
- }
-
- /**
- * Initialize StateStack to mInitialState.
- */
- private final void setupInitialStateStack() {
- if (mDbg) {
- mSm.log("setupInitialStateStack: E mInitialState=" + mInitialState.getName());
- }
-
- StateInfo curStateInfo = mStateInfo.get(mInitialState);
- for (mTempStateStackCount = 0; curStateInfo != null; mTempStateStackCount++) {
- mTempStateStack[mTempStateStackCount] = curStateInfo;
- curStateInfo = curStateInfo.parentStateInfo;
- }
-
- // Empty the StateStack
- mStateStackTopIndex = -1;
-
- moveTempStateStackToStateStack();
- }
-
- /**
- * @return current message
- */
- private final Message getCurrentMessage() {
- return mMsg;
- }
-
- /**
- * @return current state
- */
- private final IState getCurrentState() {
- return mStateStack[mStateStackTopIndex].state;
- }
-
- /**
- * Add a new state to the state machine. Bottom up addition
- * of states is allowed but the same state may only exist
- * in one hierarchy.
- *
- * @param state the state to add
- * @param parent the parent of state
- * @return stateInfo for this state
- */
- private final StateInfo addState(State state, State parent) {
- if (mDbg) {
- mSm.log("addStateInternal: E state=" + state.getName() + ",parent="
- + ((parent == null) ? "" : parent.getName()));
- }
- StateInfo parentStateInfo = null;
- if (parent != null) {
- parentStateInfo = mStateInfo.get(parent);
- if (parentStateInfo == null) {
- // Recursively add our parent as it's not been added yet.
- parentStateInfo = addState(parent, null);
- }
- }
- StateInfo stateInfo = mStateInfo.get(state);
- if (stateInfo == null) {
- stateInfo = new StateInfo();
- mStateInfo.put(state, stateInfo);
- }
-
- // Validate that we aren't adding the same state in two different hierarchies.
- if ((stateInfo.parentStateInfo != null)
- && (stateInfo.parentStateInfo != parentStateInfo)) {
- throw new RuntimeException("state already added");
- }
- stateInfo.state = state;
- stateInfo.parentStateInfo = parentStateInfo;
- stateInfo.active = false;
- if (mDbg) mSm.log("addStateInternal: X stateInfo: " + stateInfo);
- return stateInfo;
- }
-
- /**
- * Remove a state from the state machine. Will not remove the state if it is currently
- * active or if it has any children in the hierarchy.
- * @param state the state to remove
- */
- private void removeState(State state) {
- StateInfo stateInfo = mStateInfo.get(state);
- if (stateInfo == null || stateInfo.active) {
- return;
- }
- boolean isParent = mStateInfo.values().stream()
- .filter(si -> si.parentStateInfo == stateInfo)
- .findAny()
- .isPresent();
- if (isParent) {
- return;
- }
- mStateInfo.remove(state);
- }
-
- /**
- * Constructor
- *
- * @param looper for dispatching messages
- * @param sm the hierarchical state machine
- */
- private SmHandler(Looper looper, StateMachine sm) {
- super(looper);
- mSm = sm;
-
- addState(mHaltingState, null);
- addState(mQuittingState, null);
- }
-
- /** @see StateMachine#setInitialState(State) */
- private final void setInitialState(State initialState) {
- if (mDbg) mSm.log("setInitialState: initialState=" + initialState.getName());
- mInitialState = initialState;
- }
-
- /** @see StateMachine#transitionTo(IState) */
- private final void transitionTo(IState destState) {
- if (mTransitionInProgress) {
- Log.wtf(mSm.mName, "transitionTo called while transition already in progress to " +
- mDestState + ", new target state=" + destState);
- }
- mDestState = (State) destState;
- if (mDbg) mSm.log("transitionTo: destState=" + mDestState.getName());
- }
-
- /** @see StateMachine#deferMessage(Message) */
- private final void deferMessage(Message msg) {
- if (mDbg) mSm.log("deferMessage: msg=" + msg.what);
-
- /* Copy the "msg" to "newMsg" as "msg" will be recycled */
- Message newMsg = obtainMessage();
- newMsg.copyFrom(msg);
-
- mDeferredMessages.add(newMsg);
- }
-
- /** @see StateMachine#quit() */
- private final void quit() {
- if (mDbg) mSm.log("quit:");
- sendMessage(obtainMessage(SM_QUIT_CMD, sSmHandlerObj));
- }
-
- /** @see StateMachine#quitNow() */
- private final void quitNow() {
- if (mDbg) mSm.log("quitNow:");
- sendMessageAtFrontOfQueue(obtainMessage(SM_QUIT_CMD, sSmHandlerObj));
- }
-
- /** Validate that the message was sent by quit or quitNow. */
- private final boolean isQuit(Message msg) {
- return (msg.what == SM_QUIT_CMD) && (msg.obj == sSmHandlerObj);
- }
-
- /** @see StateMachine#isDbg() */
- private final boolean isDbg() {
- return mDbg;
- }
-
- /** @see StateMachine#setDbg(boolean) */
- private final void setDbg(boolean dbg) {
- mDbg = dbg;
- }
-
- }
-
- private SmHandler mSmHandler;
- private HandlerThread mSmThread;
-
- /**
- * Initialize.
- *
- * @param looper for this state machine
- * @param name of the state machine
- */
- private void initStateMachine(String name, Looper looper) {
- mName = name;
- mSmHandler = new SmHandler(looper, this);
- }
-
- /**
- * Constructor creates a StateMachine with its own thread.
- *
- * @param name of the state machine
- */
- @UnsupportedAppUsage
- protected StateMachine(String name) {
- mSmThread = new HandlerThread(name);
- mSmThread.start();
- Looper looper = mSmThread.getLooper();
-
- initStateMachine(name, looper);
- }
-
- /**
- * Constructor creates a StateMachine using the looper.
- *
- * @param name of the state machine
- */
- @UnsupportedAppUsage
- protected StateMachine(String name, Looper looper) {
- initStateMachine(name, looper);
- }
-
- /**
- * Constructor creates a StateMachine using the handler.
- *
- * @param name of the state machine
- */
- @UnsupportedAppUsage
- protected StateMachine(String name, Handler handler) {
- initStateMachine(name, handler.getLooper());
- }
-
- /**
- * Notifies subclass that the StateMachine handler is about to process the Message msg
- * @param msg The message that is being handled
- */
- protected void onPreHandleMessage(Message msg) {
- }
-
- /**
- * Notifies subclass that the StateMachine handler has finished processing the Message msg and
- * has possibly transitioned to a new state.
- * @param msg The message that is being handled
- */
- protected void onPostHandleMessage(Message msg) {
- }
-
- /**
- * Add a new state to the state machine
- * @param state the state to add
- * @param parent the parent of state
- */
- public final void addState(State state, State parent) {
- mSmHandler.addState(state, parent);
- }
-
- /**
- * Add a new state to the state machine, parent will be null
- * @param state to add
- */
- @UnsupportedAppUsage
- public final void addState(State state) {
- mSmHandler.addState(state, null);
- }
-
- /**
- * Removes a state from the state machine, unless it is currently active or if it has children.
- * @param state state to remove
- */
- public final void removeState(State state) {
- mSmHandler.removeState(state);
- }
-
- /**
- * Set the initial state. This must be invoked before
- * and messages are sent to the state machine.
- *
- * @param initialState is the state which will receive the first message.
- */
- @UnsupportedAppUsage
- public final void setInitialState(State initialState) {
- mSmHandler.setInitialState(initialState);
- }
-
- /**
- * @return current message
- */
- public final Message getCurrentMessage() {
- // mSmHandler can be null if the state machine has quit.
- SmHandler smh = mSmHandler;
- if (smh == null) return null;
- return smh.getCurrentMessage();
- }
-
- /**
- * @return current state
- */
- public final IState getCurrentState() {
- // mSmHandler can be null if the state machine has quit.
- SmHandler smh = mSmHandler;
- if (smh == null) return null;
- return smh.getCurrentState();
- }
-
- /**
- * transition to destination state. Upon returning
- * from processMessage the current state's exit will
- * be executed and upon the next message arriving
- * destState.enter will be invoked.
- *
- * this function can also be called inside the enter function of the
- * previous transition target, but the behavior is undefined when it is
- * called mid-way through a previous transition (for example, calling this
- * in the enter() routine of a intermediate node when the current transition
- * target is one of the nodes descendants).
- *
- * @param destState will be the state that receives the next message.
- */
- @UnsupportedAppUsage
- public final void transitionTo(IState destState) {
- mSmHandler.transitionTo(destState);
- }
-
- /**
- * transition to halt state. Upon returning
- * from processMessage we will exit all current
- * states, execute the onHalting() method and then
- * for all subsequent messages haltedProcessMessage
- * will be called.
- */
- public final void transitionToHaltingState() {
- mSmHandler.transitionTo(mSmHandler.mHaltingState);
- }
-
- /**
- * Defer this message until next state transition.
- * Upon transitioning all deferred messages will be
- * placed on the queue and reprocessed in the original
- * order. (i.e. The next state the oldest messages will
- * be processed first)
- *
- * @param msg is deferred until the next transition.
- */
- public final void deferMessage(Message msg) {
- mSmHandler.deferMessage(msg);
- }
-
- /**
- * Called when message wasn't handled
- *
- * @param msg that couldn't be handled.
- */
- protected void unhandledMessage(Message msg) {
- if (mSmHandler.mDbg) loge(" - unhandledMessage: msg.what=" + msg.what);
- }
-
- /**
- * Called for any message that is received after
- * transitionToHalting is called.
- */
- protected void haltedProcessMessage(Message msg) {
- }
-
- /**
- * This will be called once after handling a message that called
- * transitionToHalting. All subsequent messages will invoke
- * {@link StateMachine#haltedProcessMessage(Message)}
- */
- protected void onHalting() {
- }
-
- /**
- * This will be called once after a quit message that was NOT handled by
- * the derived StateMachine. The StateMachine will stop and any subsequent messages will be
- * ignored. In addition, if this StateMachine created the thread, the thread will
- * be stopped after this method returns.
- */
- protected void onQuitting() {
- }
-
- /**
- * @return the name
- */
- public final String getName() {
- return mName;
- }
-
- /**
- * Set number of log records to maintain and clears all current records.
- *
- * @param maxSize number of messages to maintain at anyone time.
- */
- public final void setLogRecSize(int maxSize) {
- mSmHandler.mLogRecords.setSize(maxSize);
- }
-
- /**
- * Set to log only messages that cause a state transition
- *
- * @param enable {@code true} to enable, {@code false} to disable
- */
- public final void setLogOnlyTransitions(boolean enable) {
- mSmHandler.mLogRecords.setLogOnlyTransitions(enable);
- }
-
- /**
- * @return the number of log records currently readable
- */
- public final int getLogRecSize() {
- // mSmHandler can be null if the state machine has quit.
- SmHandler smh = mSmHandler;
- if (smh == null) return 0;
- return smh.mLogRecords.size();
- }
-
- /**
- * @return the number of log records we can store
- */
- @VisibleForTesting
- public final int getLogRecMaxSize() {
- // mSmHandler can be null if the state machine has quit.
- SmHandler smh = mSmHandler;
- if (smh == null) return 0;
- return smh.mLogRecords.mMaxSize;
- }
-
- /**
- * @return the total number of records processed
- */
- public final int getLogRecCount() {
- // mSmHandler can be null if the state machine has quit.
- SmHandler smh = mSmHandler;
- if (smh == null) return 0;
- return smh.mLogRecords.count();
- }
-
- /**
- * @return a log record, or null if index is out of range
- */
- public final LogRec getLogRec(int index) {
- // mSmHandler can be null if the state machine has quit.
- SmHandler smh = mSmHandler;
- if (smh == null) return null;
- return smh.mLogRecords.get(index);
- }
-
- /**
- * @return a copy of LogRecs as a collection
- */
- public final Collection<LogRec> copyLogRecs() {
- Vector<LogRec> vlr = new Vector<LogRec>();
- SmHandler smh = mSmHandler;
- if (smh != null) {
- for (LogRec lr : smh.mLogRecords.mLogRecVector) {
- vlr.add(lr);
- }
- }
- return vlr;
- }
-
- /**
- * Add the string to LogRecords.
- *
- * @param string
- */
- public void addLogRec(String string) {
- // mSmHandler can be null if the state machine has quit.
- SmHandler smh = mSmHandler;
- if (smh == null) return;
- smh.mLogRecords.add(this, smh.getCurrentMessage(), string, smh.getCurrentState(),
- smh.mStateStack[smh.mStateStackTopIndex].state, smh.mDestState);
- }
-
- /**
- * @return true if msg should be saved in the log, default is true.
- */
- protected boolean recordLogRec(Message msg) {
- return true;
- }
-
- /**
- * Return a string to be logged by LogRec, default
- * is an empty string. Override if additional information is desired.
- *
- * @param msg that was processed
- * @return information to be logged as a String
- */
- protected String getLogRecString(Message msg) {
- return "";
- }
-
- /**
- * @return the string for msg.what
- */
- protected String getWhatToString(int what) {
- return null;
- }
-
- /**
- * @return Handler, maybe null if state machine has quit.
- */
- public final Handler getHandler() {
- return mSmHandler;
- }
-
- /**
- * Get a message and set Message.target state machine handler.
- *
- * Note: The handler can be null if the state machine has quit,
- * which means target will be null and may cause a AndroidRuntimeException
- * in MessageQueue#enqueMessage if sent directly or if sent using
- * StateMachine#sendMessage the message will just be ignored.
- *
- * @return A Message object from the global pool
- */
- public final Message obtainMessage() {
- return Message.obtain(mSmHandler);
- }
-
- /**
- * Get a message and set Message.target state machine handler, what.
- *
- * Note: The handler can be null if the state machine has quit,
- * which means target will be null and may cause a AndroidRuntimeException
- * in MessageQueue#enqueMessage if sent directly or if sent using
- * StateMachine#sendMessage the message will just be ignored.
- *
- * @param what is the assigned to Message.what.
- * @return A Message object from the global pool
- */
- public final Message obtainMessage(int what) {
- return Message.obtain(mSmHandler, what);
- }
-
- /**
- * Get a message and set Message.target state machine handler,
- * what and obj.
- *
- * Note: The handler can be null if the state machine has quit,
- * which means target will be null and may cause a AndroidRuntimeException
- * in MessageQueue#enqueMessage if sent directly or if sent using
- * StateMachine#sendMessage the message will just be ignored.
- *
- * @param what is the assigned to Message.what.
- * @param obj is assigned to Message.obj.
- * @return A Message object from the global pool
- */
- public final Message obtainMessage(int what, Object obj) {
- return Message.obtain(mSmHandler, what, obj);
- }
-
- /**
- * Get a message and set Message.target state machine handler,
- * what, arg1 and arg2
- *
- * Note: The handler can be null if the state machine has quit,
- * which means target will be null and may cause a AndroidRuntimeException
- * in MessageQueue#enqueMessage if sent directly or if sent using
- * StateMachine#sendMessage the message will just be ignored.
- *
- * @param what is assigned to Message.what
- * @param arg1 is assigned to Message.arg1
- * @return A Message object from the global pool
- */
- public final Message obtainMessage(int what, int arg1) {
- // use this obtain so we don't match the obtain(h, what, Object) method
- return Message.obtain(mSmHandler, what, arg1, 0);
- }
-
- /**
- * Get a message and set Message.target state machine handler,
- * what, arg1 and arg2
- *
- * Note: The handler can be null if the state machine has quit,
- * which means target will be null and may cause a AndroidRuntimeException
- * in MessageQueue#enqueMessage if sent directly or if sent using
- * StateMachine#sendMessage the message will just be ignored.
- *
- * @param what is assigned to Message.what
- * @param arg1 is assigned to Message.arg1
- * @param arg2 is assigned to Message.arg2
- * @return A Message object from the global pool
- */
- @UnsupportedAppUsage
- public final Message obtainMessage(int what, int arg1, int arg2) {
- return Message.obtain(mSmHandler, what, arg1, arg2);
- }
-
- /**
- * Get a message and set Message.target state machine handler,
- * what, arg1, arg2 and obj
- *
- * Note: The handler can be null if the state machine has quit,
- * which means target will be null and may cause a AndroidRuntimeException
- * in MessageQueue#enqueMessage if sent directly or if sent using
- * StateMachine#sendMessage the message will just be ignored.
- *
- * @param what is assigned to Message.what
- * @param arg1 is assigned to Message.arg1
- * @param arg2 is assigned to Message.arg2
- * @param obj is assigned to Message.obj
- * @return A Message object from the global pool
- */
- @UnsupportedAppUsage
- public final Message obtainMessage(int what, int arg1, int arg2, Object obj) {
- return Message.obtain(mSmHandler, what, arg1, arg2, obj);
- }
-
- /**
- * Enqueue a message to this state machine.
- *
- * Message is ignored if state machine has quit.
- */
- @UnsupportedAppUsage
- public void sendMessage(int what) {
- // mSmHandler can be null if the state machine has quit.
- SmHandler smh = mSmHandler;
- if (smh == null) return;
-
- smh.sendMessage(obtainMessage(what));
- }
-
- /**
- * Enqueue a message to this state machine.
- *
- * Message is ignored if state machine has quit.
- */
- @UnsupportedAppUsage
- public void sendMessage(int what, Object obj) {
- // mSmHandler can be null if the state machine has quit.
- SmHandler smh = mSmHandler;
- if (smh == null) return;
-
- smh.sendMessage(obtainMessage(what, obj));
- }
-
- /**
- * Enqueue a message to this state machine.
- *
- * Message is ignored if state machine has quit.
- */
- @UnsupportedAppUsage
- public void sendMessage(int what, int arg1) {
- // mSmHandler can be null if the state machine has quit.
- SmHandler smh = mSmHandler;
- if (smh == null) return;
-
- smh.sendMessage(obtainMessage(what, arg1));
- }
-
- /**
- * Enqueue a message to this state machine.
- *
- * Message is ignored if state machine has quit.
- */
- public void sendMessage(int what, int arg1, int arg2) {
- // mSmHandler can be null if the state machine has quit.
- SmHandler smh = mSmHandler;
- if (smh == null) return;
-
- smh.sendMessage(obtainMessage(what, arg1, arg2));
- }
-
- /**
- * Enqueue a message to this state machine.
- *
- * Message is ignored if state machine has quit.
- */
- @UnsupportedAppUsage
- public void sendMessage(int what, int arg1, int arg2, Object obj) {
- // mSmHandler can be null if the state machine has quit.
- SmHandler smh = mSmHandler;
- if (smh == null) return;
-
- smh.sendMessage(obtainMessage(what, arg1, arg2, obj));
- }
-
- /**
- * Enqueue a message to this state machine.
- *
- * Message is ignored if state machine has quit.
- */
- @UnsupportedAppUsage
- public void sendMessage(Message msg) {
- // mSmHandler can be null if the state machine has quit.
- SmHandler smh = mSmHandler;
- if (smh == null) return;
-
- smh.sendMessage(msg);
- }
-
- /**
- * Enqueue a message to this state machine after a delay.
- *
- * Message is ignored if state machine has quit.
- */
- public void sendMessageDelayed(int what, long delayMillis) {
- // mSmHandler can be null if the state machine has quit.
- SmHandler smh = mSmHandler;
- if (smh == null) return;
-
- smh.sendMessageDelayed(obtainMessage(what), delayMillis);
- }
-
- /**
- * Enqueue a message to this state machine after a delay.
- *
- * Message is ignored if state machine has quit.
- */
- public void sendMessageDelayed(int what, Object obj, long delayMillis) {
- // mSmHandler can be null if the state machine has quit.
- SmHandler smh = mSmHandler;
- if (smh == null) return;
-
- smh.sendMessageDelayed(obtainMessage(what, obj), delayMillis);
- }
-
- /**
- * Enqueue a message to this state machine after a delay.
- *
- * Message is ignored if state machine has quit.
- */
- public void sendMessageDelayed(int what, int arg1, long delayMillis) {
- // mSmHandler can be null if the state machine has quit.
- SmHandler smh = mSmHandler;
- if (smh == null) return;
-
- smh.sendMessageDelayed(obtainMessage(what, arg1), delayMillis);
- }
-
- /**
- * Enqueue a message to this state machine after a delay.
- *
- * Message is ignored if state machine has quit.
- */
- public void sendMessageDelayed(int what, int arg1, int arg2, long delayMillis) {
- // mSmHandler can be null if the state machine has quit.
- SmHandler smh = mSmHandler;
- if (smh == null) return;
-
- smh.sendMessageDelayed(obtainMessage(what, arg1, arg2), delayMillis);
- }
-
- /**
- * Enqueue a message to this state machine after a delay.
- *
- * Message is ignored if state machine has quit.
- */
- public void sendMessageDelayed(int what, int arg1, int arg2, Object obj,
- long delayMillis) {
- // mSmHandler can be null if the state machine has quit.
- SmHandler smh = mSmHandler;
- if (smh == null) return;
-
- smh.sendMessageDelayed(obtainMessage(what, arg1, arg2, obj), delayMillis);
- }
-
- /**
- * Enqueue a message to this state machine after a delay.
- *
- * Message is ignored if state machine has quit.
- */
- public void sendMessageDelayed(Message msg, long delayMillis) {
- // mSmHandler can be null if the state machine has quit.
- SmHandler smh = mSmHandler;
- if (smh == null) return;
-
- smh.sendMessageDelayed(msg, delayMillis);
- }
-
- /**
- * Enqueue a message to the front of the queue for this state machine.
- * Protected, may only be called by instances of StateMachine.
- *
- * Message is ignored if state machine has quit.
- */
- protected final void sendMessageAtFrontOfQueue(int what) {
- // mSmHandler can be null if the state machine has quit.
- SmHandler smh = mSmHandler;
- if (smh == null) return;
-
- smh.sendMessageAtFrontOfQueue(obtainMessage(what));
- }
-
- /**
- * Enqueue a message to the front of the queue for this state machine.
- * Protected, may only be called by instances of StateMachine.
- *
- * Message is ignored if state machine has quit.
- */
- protected final void sendMessageAtFrontOfQueue(int what, Object obj) {
- // mSmHandler can be null if the state machine has quit.
- SmHandler smh = mSmHandler;
- if (smh == null) return;
-
- smh.sendMessageAtFrontOfQueue(obtainMessage(what, obj));
- }
-
- /**
- * Enqueue a message to the front of the queue for this state machine.
- * Protected, may only be called by instances of StateMachine.
- *
- * Message is ignored if state machine has quit.
- */
- protected final void sendMessageAtFrontOfQueue(int what, int arg1) {
- // mSmHandler can be null if the state machine has quit.
- SmHandler smh = mSmHandler;
- if (smh == null) return;
-
- smh.sendMessageAtFrontOfQueue(obtainMessage(what, arg1));
- }
-
-
- /**
- * Enqueue a message to the front of the queue for this state machine.
- * Protected, may only be called by instances of StateMachine.
- *
- * Message is ignored if state machine has quit.
- */
- protected final void sendMessageAtFrontOfQueue(int what, int arg1, int arg2) {
- // mSmHandler can be null if the state machine has quit.
- SmHandler smh = mSmHandler;
- if (smh == null) return;
-
- smh.sendMessageAtFrontOfQueue(obtainMessage(what, arg1, arg2));
- }
-
- /**
- * Enqueue a message to the front of the queue for this state machine.
- * Protected, may only be called by instances of StateMachine.
- *
- * Message is ignored if state machine has quit.
- */
- protected final void sendMessageAtFrontOfQueue(int what, int arg1, int arg2, Object obj) {
- // mSmHandler can be null if the state machine has quit.
- SmHandler smh = mSmHandler;
- if (smh == null) return;
-
- smh.sendMessageAtFrontOfQueue(obtainMessage(what, arg1, arg2, obj));
- }
-
- /**
- * Enqueue a message to the front of the queue for this state machine.
- * Protected, may only be called by instances of StateMachine.
- *
- * Message is ignored if state machine has quit.
- */
- protected final void sendMessageAtFrontOfQueue(Message msg) {
- // mSmHandler can be null if the state machine has quit.
- SmHandler smh = mSmHandler;
- if (smh == null) return;
-
- smh.sendMessageAtFrontOfQueue(msg);
- }
-
- /**
- * Removes a message from the message queue.
- * Protected, may only be called by instances of StateMachine.
- */
- protected final void removeMessages(int what) {
- // mSmHandler can be null if the state machine has quit.
- SmHandler smh = mSmHandler;
- if (smh == null) return;
-
- smh.removeMessages(what);
- }
-
- /**
- * Removes a message from the deferred messages queue.
- */
- protected final void removeDeferredMessages(int what) {
- SmHandler smh = mSmHandler;
- if (smh == null) return;
-
- Iterator<Message> iterator = smh.mDeferredMessages.iterator();
- while (iterator.hasNext()) {
- Message msg = iterator.next();
- if (msg.what == what) iterator.remove();
- }
- }
-
- /**
- * Check if there are any pending messages with code 'what' in deferred messages queue.
- */
- protected final boolean hasDeferredMessages(int what) {
- SmHandler smh = mSmHandler;
- if (smh == null) return false;
-
- Iterator<Message> iterator = smh.mDeferredMessages.iterator();
- while (iterator.hasNext()) {
- Message msg = iterator.next();
- if (msg.what == what) return true;
- }
-
- return false;
- }
-
- /**
- * Check if there are any pending posts of messages with code 'what' in
- * the message queue. This does NOT check messages in deferred message queue.
- */
- protected final boolean hasMessages(int what) {
- SmHandler smh = mSmHandler;
- if (smh == null) return false;
-
- return smh.hasMessages(what);
- }
-
- /**
- * Validate that the message was sent by
- * {@link StateMachine#quit} or {@link StateMachine#quitNow}.
- * */
- protected final boolean isQuit(Message msg) {
- // mSmHandler can be null if the state machine has quit.
- SmHandler smh = mSmHandler;
- if (smh == null) return msg.what == SM_QUIT_CMD;
-
- return smh.isQuit(msg);
- }
-
- /**
- * Quit the state machine after all currently queued up messages are processed.
- */
- public final void quit() {
- // mSmHandler can be null if the state machine is already stopped.
- SmHandler smh = mSmHandler;
- if (smh == null) return;
-
- smh.quit();
- }
-
- /**
- * Quit the state machine immediately all currently queued messages will be discarded.
- */
- public final void quitNow() {
- // mSmHandler can be null if the state machine is already stopped.
- SmHandler smh = mSmHandler;
- if (smh == null) return;
-
- smh.quitNow();
- }
-
- /**
- * @return if debugging is enabled
- */
- public boolean isDbg() {
- // mSmHandler can be null if the state machine has quit.
- SmHandler smh = mSmHandler;
- if (smh == null) return false;
-
- return smh.isDbg();
- }
-
- /**
- * Set debug enable/disabled.
- *
- * @param dbg is true to enable debugging.
- */
- public void setDbg(boolean dbg) {
- // mSmHandler can be null if the state machine has quit.
- SmHandler smh = mSmHandler;
- if (smh == null) return;
-
- smh.setDbg(dbg);
- }
-
- /**
- * Start the state machine.
- */
- @UnsupportedAppUsage
- public void start() {
- // mSmHandler can be null if the state machine has quit.
- SmHandler smh = mSmHandler;
- if (smh == null) return;
-
- /** Send the complete construction message */
- smh.completeConstruction();
- }
-
- /**
- * Dump the current state.
- *
- * @param fd
- * @param pw
- * @param args
- */
- @UnsupportedAppUsage
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println(getName() + ":");
- pw.println(" total records=" + getLogRecCount());
- for (int i = 0; i < getLogRecSize(); i++) {
- pw.println(" rec[" + i + "]: " + getLogRec(i).toString());
- pw.flush();
- }
- pw.println("curState=" + getCurrentState().getName());
- }
-
- @Override
- public String toString() {
- String name = "(null)";
- String state = "(null)";
- try {
- name = mName.toString();
- state = mSmHandler.getCurrentState().getName().toString();
- } catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
- // Will use default(s) initialized above.
- }
- return "name=" + name + " state=" + state;
- }
-
- /**
- * Log with debug and add to the LogRecords.
- *
- * @param s is string log
- */
- protected void logAndAddLogRec(String s) {
- addLogRec(s);
- log(s);
- }
-
- /**
- * Log with debug
- *
- * @param s is string log
- */
- protected void log(String s) {
- Log.d(mName, s);
- }
-
- /**
- * Log with debug attribute
- *
- * @param s is string log
- */
- protected void logd(String s) {
- Log.d(mName, s);
- }
-
- /**
- * Log with verbose attribute
- *
- * @param s is string log
- */
- protected void logv(String s) {
- Log.v(mName, s);
- }
-
- /**
- * Log with info attribute
- *
- * @param s is string log
- */
- protected void logi(String s) {
- Log.i(mName, s);
- }
-
- /**
- * Log with warning attribute
- *
- * @param s is string log
- */
- protected void logw(String s) {
- Log.w(mName, s);
- }
-
- /**
- * Log with error attribute
- *
- * @param s is string log
- */
- protected void loge(String s) {
- Log.e(mName, s);
- }
-
- /**
- * Log with error attribute
- *
- * @param s is string log
- * @param e is a Throwable which logs additional information.
- */
- protected void loge(String s, Throwable e) {
- Log.e(mName, s, e);
- }
-}
diff --git a/src/com/android/cellbroadcastreceiver/WakeLockStateMachine.java b/src/com/android/cellbroadcastreceiver/WakeLockStateMachine.java
deleted file mode 100644
index 4de06f4f5..000000000
--- a/src/com/android/cellbroadcastreceiver/WakeLockStateMachine.java
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * Copyright (C) 2013 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.annotation.UnsupportedAppUsage;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Build;
-import android.os.Message;
-import android.os.PowerManager;
-
-import android.util.Log;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
-
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * Generic state machine for handling messages and waiting for ordered broadcasts to complete.
- * Subclasses implement {@link #handleSmsMessage}, which returns true to transition into waiting
- * state, or false to remain in idle state. The wakelock is acquired on exit from idle state,
- * and is released a few seconds after returning to idle state, or immediately upon calling
- * {@link #quit}.
- */
-public abstract class WakeLockStateMachine extends StateMachine {
- protected static final boolean DBG = Build.IS_DEBUGGABLE;
-
- private final PowerManager.WakeLock mWakeLock;
-
- /** New message to process. */
- public static final int EVENT_NEW_SMS_MESSAGE = 1;
-
- /** Result receiver called for current cell broadcast. */
- protected static final int EVENT_BROADCAST_COMPLETE = 2;
-
- /** Release wakelock after a short timeout when returning to idle state. */
- static final int EVENT_RELEASE_WAKE_LOCK = 3;
-
- @UnsupportedAppUsage
- protected Context mContext;
-
- protected AtomicInteger mReceiverCount = new AtomicInteger(0);
-
- /** Wakelock release delay when returning to idle state. */
- private static final int WAKE_LOCK_TIMEOUT = 3000;
-
- private final DefaultState mDefaultState = new DefaultState();
- @UnsupportedAppUsage
- private final IdleState mIdleState = new IdleState();
- private final WaitingState mWaitingState = new WaitingState();
-
- protected WakeLockStateMachine(String debugTag, Context context) {
- super(debugTag);
-
- mContext = context;
-
- PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
- mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, debugTag);
- // wake lock released after we enter idle state
- mWakeLock.acquire();
-
- addState(mDefaultState);
- addState(mIdleState, mDefaultState);
- addState(mWaitingState, mDefaultState);
- setInitialState(mIdleState);
- }
-
- private void releaseWakeLock() {
- if (mWakeLock.isHeld()) {
- mWakeLock.release();
- }
-
- if (mWakeLock.isHeld()) {
- loge("Wait lock is held after release.");
- }
- }
-
- /**
- * Tell the state machine to quit after processing all messages.
- */
- public final void dispose() {
- quit();
- }
-
- @Override
- protected void onQuitting() {
- // fully release the wakelock
- while (mWakeLock.isHeld()) {
- mWakeLock.release();
- }
- }
-
- /**
- * Send a message with the specified object for {@link #handleSmsMessage}.
- * @param obj the object to pass in the msg.obj field
- */
- public final void dispatchSmsMessage(Object obj) {
- sendMessage(EVENT_NEW_SMS_MESSAGE, obj);
- }
-
- /**
- * This parent state throws an exception (for debug builds) or prints an error for unhandled
- * message types.
- */
- class DefaultState extends State {
- @Override
- public boolean processMessage(Message msg) {
- switch (msg.what) {
- default: {
- String errorText = "processMessage: unhandled message type " + msg.what;
- if (Build.IS_DEBUGGABLE) {
- throw new RuntimeException(errorText);
- } else {
- loge(errorText);
- }
- break;
- }
- }
- return HANDLED;
- }
- }
-
- /**
- * Idle state delivers Cell Broadcasts to receivers. It acquires the wakelock, which is
- * released when the broadcast completes.
- */
- class IdleState extends State {
- @Override
- public void enter() {
- sendMessageDelayed(EVENT_RELEASE_WAKE_LOCK, WAKE_LOCK_TIMEOUT);
- }
-
- @Override
- public void exit() {
- mWakeLock.acquire();
- if (DBG) log("acquired wakelock, leaving Idle state");
- }
-
- @Override
- public boolean processMessage(Message msg) {
- switch (msg.what) {
- case EVENT_NEW_SMS_MESSAGE:
- // transition to waiting state if we sent a broadcast
- if (handleSmsMessage(msg)) {
- transitionTo(mWaitingState);
- }
- return HANDLED;
-
- case EVENT_RELEASE_WAKE_LOCK:
- releaseWakeLock();
- return HANDLED;
-
- default:
- return NOT_HANDLED;
- }
- }
- }
-
- /**
- * Waiting state waits for the result receiver to be called for the current cell broadcast.
- * In this state, any new cell broadcasts are deferred until we return to Idle state.
- */
- class WaitingState extends State {
- @Override
- public boolean processMessage(Message msg) {
- switch (msg.what) {
- case EVENT_NEW_SMS_MESSAGE:
- log("deferring message until return to idle");
- deferMessage(msg);
- return HANDLED;
-
- case EVENT_BROADCAST_COMPLETE:
- log("broadcast complete, returning to idle");
- transitionTo(mIdleState);
- return HANDLED;
-
- case EVENT_RELEASE_WAKE_LOCK:
- releaseWakeLock();
- return HANDLED;
-
- default:
- return NOT_HANDLED;
- }
- }
- }
-
- /**
- * Implemented by subclass to handle messages in {@link IdleState}.
- * @param message the message to process
- * @return true to transition to {@link WaitingState}; false to stay in {@link IdleState}
- */
- protected abstract boolean handleSmsMessage(Message message);
-
- /**
- * BroadcastReceiver to send message to return to idle state.
- */
- protected final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (mReceiverCount.decrementAndGet() == 0) {
- sendMessage(EVENT_BROADCAST_COMPLETE);
- }
- }
- };
-
- /**
- * Log with debug level.
- * @param s the string to log
- */
- @UnsupportedAppUsage
- @Override
- protected void log(String s) {
- Log.d(getName(), s);
- }
-
- /**
- * Log with error level.
- * @param s the string to log
- */
- @Override
- protected void loge(String s) {
- Log.e(getName(), s);
- }
-
- /**
- * Log with error level.
- * @param s the string to log
- * @param e is a Throwable which logs additional information.
- */
- @Override
- protected void loge(String s, Throwable e) {
- Log.e(getName(), s, e);
- }
-}
diff --git a/tests/testapp/src/com/android/cellbroadcastreceiver/tests/SendCdmaCmasMessages.java b/tests/testapp/src/com/android/cellbroadcastreceiver/tests/SendCdmaCmasMessages.java
index 24ab11b7e..0103068a6 100644
--- a/tests/testapp/src/com/android/cellbroadcastreceiver/tests/SendCdmaCmasMessages.java
+++ b/tests/testapp/src/com/android/cellbroadcastreceiver/tests/SendCdmaCmasMessages.java
@@ -132,7 +132,7 @@ public class SendCdmaCmasMessages {
return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP2,
SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE, serialNumber,
new SmsCbLocation("123456"), serviceCategory, language, body,
- SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, null, cmasInfo);
+ SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, null, cmasInfo, 0 /*subId*/);
}
/**
diff --git a/tests/testapp/src/com/android/cellbroadcastreceiver/tests/SendGsmCmasMessages.java b/tests/testapp/src/com/android/cellbroadcastreceiver/tests/SendGsmCmasMessages.java
index c9082a97d..5a3d0b993 100644
--- a/tests/testapp/src/com/android/cellbroadcastreceiver/tests/SendGsmCmasMessages.java
+++ b/tests/testapp/src/com/android/cellbroadcastreceiver/tests/SendGsmCmasMessages.java
@@ -218,7 +218,7 @@ public class SendGsmCmasMessages {
certainty);
return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP, 0, serialNumber,
new SmsCbLocation("123456"), serviceCategory, language, body,
- priority, null, cmasInfo);
+ priority, null, cmasInfo, 0/*slotIndex*/);
}
/**
diff --git a/tests/testapp/src/com/android/cellbroadcastreceiver/tests/SendTestMessages.java b/tests/testapp/src/com/android/cellbroadcastreceiver/tests/SendTestMessages.java
index fef8f5d94..9cee85d31 100644
--- a/tests/testapp/src/com/android/cellbroadcastreceiver/tests/SendTestMessages.java
+++ b/tests/testapp/src/com/android/cellbroadcastreceiver/tests/SendTestMessages.java
@@ -423,7 +423,7 @@ public class SendTestMessages {
}
}
return GsmSmsCbMessage.createSmsCbMessage(context, new SmsCbHeader(pdus[0]),
- sEmptyLocation, pdus);
+ sEmptyLocation, pdus, 0 /* slotIndex */);
} catch (IllegalArgumentException e) {
return null;
}
diff --git a/tests/unit/AndroidTest.xml b/tests/unit/AndroidTest.xml
index 328fecb1f..8e676981b 100644
--- a/tests/unit/AndroidTest.xml
+++ b/tests/unit/AndroidTest.xml
@@ -15,7 +15,7 @@
-->
<configuration description="Run CellBroadcastReceiver Unit Test Cases.">
<target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
- <option name="test-file-name" value="CellBroadcastReceiverPlatformUnitTests.apk" />
+ <option name="test-file-name" value="CellBroadcastReceiverUnitTests.apk" />
</target_preparer>
<option name="test-suite-tag" value="apct" />
diff --git a/tests/unit/src/com/android/cellbroadcastreceiver/CellBroadcastAlertDialogTest.java b/tests/unit/src/com/android/cellbroadcastreceiver/CellBroadcastAlertDialogTest.java
index 29a28b2c0..db8c4c938 100644
--- a/tests/unit/src/com/android/cellbroadcastreceiver/CellBroadcastAlertDialogTest.java
+++ b/tests/unit/src/com/android/cellbroadcastreceiver/CellBroadcastAlertDialogTest.java
@@ -26,7 +26,6 @@ import android.content.Intent;
import android.os.Bundle;
import android.os.IPowerManager;
import android.os.PowerManager;
-import android.telephony.CellBroadcastMessage;
import android.widget.TextView;
import org.junit.After;
diff --git a/tests/unit/src/com/android/cellbroadcastreceiver/CellBroadcastAlertServiceTest.java b/tests/unit/src/com/android/cellbroadcastreceiver/CellBroadcastAlertServiceTest.java
index 727df077c..86096c9fa 100644
--- a/tests/unit/src/com/android/cellbroadcastreceiver/CellBroadcastAlertServiceTest.java
+++ b/tests/unit/src/com/android/cellbroadcastreceiver/CellBroadcastAlertServiceTest.java
@@ -27,7 +27,6 @@ import android.content.Intent;
import android.os.PersistableBundle;
import android.provider.Telephony;
import android.telephony.CarrierConfigManager;
-import android.telephony.CellBroadcastMessage;
import android.telephony.SmsCbCmasInfo;
import android.telephony.SmsCbEtwsInfo;
import android.telephony.SmsCbLocation;
@@ -50,7 +49,8 @@ public class CellBroadcastAlertServiceTest extends
static SmsCbMessage createMessage(int serialNumber) {
return new SmsCbMessage(1, 2, serialNumber, new SmsCbLocation(),
SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL, "language", "body",
- SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, null, new SmsCbCmasInfo(0, 2, 3, 4, 5, 6));
+ SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, null, new SmsCbCmasInfo(0, 2, 3, 4, 5, 6),
+ 0 /*subId*/);
}
@Before
@@ -89,10 +89,9 @@ public class CellBroadcastAlertServiceTest extends
compareEtwsWarningInfo(cbm1.getEtwsWarningInfo(), cbm2.getEtwsWarningInfo());
assertEquals(cbm1.getLanguageCode(), cbm2.getLanguageCode());
assertEquals(cbm1.getMessageBody(), cbm2.getMessageBody());
- assertEquals(cbm1.getSerialNumber(), cbm2.getSerialNumber());
assertEquals(cbm1.getServiceCategory(), cbm2.getServiceCategory());
- assertEquals(cbm1.getSubId(), cbm2.getSubId());
- assertEquals(cbm1.getSerialNumber(), cbm2.getSerialNumber());
+ assertEquals(cbm1.getSmsCbMessage().getSerialNumber(),
+ cbm2.getSmsCbMessage().getSerialNumber());
}
private void sendMessage(int serialNumber) {
@@ -232,7 +231,7 @@ public class CellBroadcastAlertServiceTest extends
CellBroadcastMessage cbmTest =
(CellBroadcastMessage) mServiceIntentToVerify.getExtras().get("message");
- assertEquals(91924, cbmTest.getSerialNumber());
+ assertEquals(91924, cbmTest.getSmsCbMessage().getSerialNumber());
mServiceIntentToVerify = null;
// Wait until it expires.
@@ -242,7 +241,7 @@ public class CellBroadcastAlertServiceTest extends
// Since the previous one has already expired, this one should not be treated as a duplicate
cbmTest = (CellBroadcastMessage) mServiceIntentToVerify.getExtras().get("message");
- assertEquals(91924, cbmTest.getSerialNumber());
+ assertEquals(91924, cbmTest.getSmsCbMessage().getSerialNumber());
waitForMs(500);
mServiceIntentToVerify = null;
diff --git a/tests/unit/src/com/android/cellbroadcastreceiver/CellBroadcastChannelManagerTest.java b/tests/unit/src/com/android/cellbroadcastreceiver/CellBroadcastChannelManagerTest.java
index 42402482b..f89853198 100644
--- a/tests/unit/src/com/android/cellbroadcastreceiver/CellBroadcastChannelManagerTest.java
+++ b/tests/unit/src/com/android/cellbroadcastreceiver/CellBroadcastChannelManagerTest.java
@@ -18,6 +18,7 @@ package com.android.cellbroadcastreceiver;
import static org.junit.Assert.assertEquals;
+import android.telephony.SubscriptionManager;
import android.test.suitebuilder.annotation.SmallTest;
import com.android.cellbroadcastreceiver.CellBroadcastAlertService.AlertType;
@@ -59,9 +60,11 @@ public class CellBroadcastChannelManagerTest extends CellBroadcastTest {
"0xA804:type=test, emergency=true"
});
- ArrayList<CellBroadcastChannelRange> list =
- CellBroadcastChannelManager.getCellBroadcastChannelRanges(
- mContext, R.array.additional_cbs_channels_strings);
+ CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager(mContext,
+ SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
+
+ ArrayList<CellBroadcastChannelRange> list = channelManager.getCellBroadcastChannelRanges(
+ R.array.additional_cbs_channels_strings);
assertEquals(12, list.get(0).mStartId);
assertEquals(12, list.get(0).mEndId);
diff --git a/tests/unit/src/com/android/cellbroadcastreceiver/CellBroadcastConfigServiceTest.java b/tests/unit/src/com/android/cellbroadcastreceiver/CellBroadcastConfigServiceTest.java
index 186bfeb14..c89d84873 100644
--- a/tests/unit/src/com/android/cellbroadcastreceiver/CellBroadcastConfigServiceTest.java
+++ b/tests/unit/src/com/android/cellbroadcastreceiver/CellBroadcastConfigServiceTest.java
@@ -29,6 +29,7 @@ import android.content.Context;
import android.content.ContextWrapper;
import android.content.SharedPreferences;
import android.telephony.SmsManager;
+import android.telephony.SubscriptionManager;
import android.test.suitebuilder.annotation.SmallTest;
import com.android.cellbroadcastreceiver.CellBroadcastChannelManager.CellBroadcastChannelRange;
@@ -59,8 +60,6 @@ public class CellBroadcastConfigServiceTest extends CellBroadcastTest {
private CellBroadcastConfigService mConfigService;
- private SmsManager mSmsManager = SmsManager.getDefault();
-
@Before
public void setUp() throws Exception {
super.setUp(getClass().getSimpleName());
@@ -126,11 +125,12 @@ public class CellBroadcastConfigServiceTest extends CellBroadcastTest {
super.tearDown();
}
- private void setCellBroadcastRange(boolean enable, List<CellBroadcastChannelRange> ranges)
+ private void setCellBroadcastRange(int subId, boolean enable,
+ List<CellBroadcastChannelRange> ranges)
throws Exception {
Class[] cArgs = new Class[3];
- cArgs[0] = SmsManager.class;
+ cArgs[0] = Integer.TYPE;
cArgs[1] = Boolean.TYPE;
cArgs[2] = List.class;
@@ -138,7 +138,7 @@ public class CellBroadcastConfigServiceTest extends CellBroadcastTest {
CellBroadcastConfigService.class.getDeclaredMethod("setCellBroadcastRange", cArgs);
method.setAccessible(true);
- method.invoke(mConfigService, mSmsManager, enable, ranges);
+ method.invoke(mConfigService, subId, enable, ranges);
}
/**
@@ -148,8 +148,9 @@ public class CellBroadcastConfigServiceTest extends CellBroadcastTest {
@SmallTest
public void testEnableCellBroadcastRange() throws Exception {
ArrayList<CellBroadcastChannelRange> result = new ArrayList<>();
- result.add(new CellBroadcastChannelRange(mContext, "10-20"));
- setCellBroadcastRange(true, result);
+ result.add(new CellBroadcastChannelRange(mContext,
+ SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, "10-20"));
+ setCellBroadcastRange(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, true, result);
ArgumentCaptor<Integer> captorStart = ArgumentCaptor.forClass(Integer.class);
ArgumentCaptor<Integer> captorEnd = ArgumentCaptor.forClass(Integer.class);
ArgumentCaptor<Integer> captorType = ArgumentCaptor.forClass(Integer.class);
@@ -169,8 +170,9 @@ public class CellBroadcastConfigServiceTest extends CellBroadcastTest {
@SmallTest
public void testDisableCellBroadcastRange() throws Exception {
ArrayList<CellBroadcastChannelRange> result = new ArrayList<>();
- result.add(new CellBroadcastChannelRange(mContext, "10-20"));
- setCellBroadcastRange(false, result);
+ result.add(new CellBroadcastChannelRange(mContext,
+ SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, "10-20"));
+ setCellBroadcastRange(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, false, result);
ArgumentCaptor<Integer> captorStart = ArgumentCaptor.forClass(Integer.class);
ArgumentCaptor<Integer> captorEnd = ArgumentCaptor.forClass(Integer.class);
ArgumentCaptor<Integer> captorType = ArgumentCaptor.forClass(Integer.class);
@@ -198,7 +200,7 @@ public class CellBroadcastConfigServiceTest extends CellBroadcastTest {
setPreference(CellBroadcastSettings.KEY_ENABLE_CMAS_SEVERE_THREAT_ALERTS, true);
setPreference(CellBroadcastSettings.KEY_ENABLE_CMAS_AMBER_ALERTS, true);
- mConfigService.setCellBroadcastOnSub(mSmsManager, true);
+ mConfigService.enableCellBroadcastChannels(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
verify(mMockedSmsService, times(1)).enableCellBroadcastRangeForSubscriber(
eq(0),
@@ -288,7 +290,7 @@ public class CellBroadcastConfigServiceTest extends CellBroadcastTest {
@SmallTest
public void testEnablingPresidential() throws Exception {
setPreference(CellBroadcastSettings.KEY_ENABLE_ALERTS_MASTER_TOGGLE, true);
- mConfigService.setCellBroadcastOnSub(mSmsManager, true);
+ mConfigService.enableCellBroadcastChannels(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
verify(mMockedSmsService, times(1)).enableCellBroadcastRangeForSubscriber(
eq(0),
@@ -309,7 +311,7 @@ public class CellBroadcastConfigServiceTest extends CellBroadcastTest {
eq(SmsManager.CELL_BROADCAST_RAN_TYPE_GSM));
setPreference(CellBroadcastSettings.KEY_ENABLE_ALERTS_MASTER_TOGGLE, false);
- mConfigService.setCellBroadcastOnSub(mSmsManager, true);
+ mConfigService.enableCellBroadcastChannels(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
verify(mMockedSmsService, times(2)).enableCellBroadcastRangeForSubscriber(
eq(0),
@@ -330,21 +332,21 @@ public class CellBroadcastConfigServiceTest extends CellBroadcastTest {
eq(SmsManager.CELL_BROADCAST_RAN_TYPE_GSM));
setPreference(CellBroadcastSettings.KEY_ENABLE_ALERTS_MASTER_TOGGLE, true);
- mConfigService.setCellBroadcastOnSub(mSmsManager, false);
+ mConfigService.enableCellBroadcastChannels(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
- verify(mMockedSmsService, times(1)).disableCellBroadcastRangeForSubscriber(
+ verify(mMockedSmsService, times(3)).enableCellBroadcastRangeForSubscriber(
eq(0),
eq(SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT),
eq(SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT),
eq(SmsManager.CELL_BROADCAST_RAN_TYPE_CDMA));
- verify(mMockedSmsService, times(1)).disableCellBroadcastRangeForSubscriber(
+ verify(mMockedSmsService, times(3)).enableCellBroadcastRangeForSubscriber(
eq(0),
eq(SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL),
eq(SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL),
eq(SmsManager.CELL_BROADCAST_RAN_TYPE_GSM));
- verify(mMockedSmsService, times(1)).disableCellBroadcastRangeForSubscriber(
+ verify(mMockedSmsService, times(3)).enableCellBroadcastRangeForSubscriber(
eq(0),
eq(SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL_LANGUAGE),
eq(SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL_LANGUAGE),
@@ -360,7 +362,7 @@ public class CellBroadcastConfigServiceTest extends CellBroadcastTest {
public void testEnablingExtreme() throws Exception {
setPreference(CellBroadcastSettings.KEY_ENABLE_ALERTS_MASTER_TOGGLE, true);
setPreference(CellBroadcastSettings.KEY_ENABLE_CMAS_EXTREME_THREAT_ALERTS, true);
- mConfigService.setCellBroadcastOnSub(mSmsManager, true);
+ mConfigService.enableCellBroadcastChannels(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
verify(mMockedSmsService, times(1)).enableCellBroadcastRangeForSubscriber(
eq(0),
@@ -381,7 +383,7 @@ public class CellBroadcastConfigServiceTest extends CellBroadcastTest {
eq(SmsManager.CELL_BROADCAST_RAN_TYPE_GSM));
setPreference(CellBroadcastSettings.KEY_ENABLE_CMAS_EXTREME_THREAT_ALERTS, false);
- mConfigService.setCellBroadcastOnSub(mSmsManager, true);
+ mConfigService.enableCellBroadcastChannels(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
verify(mMockedSmsService, times(1)).disableCellBroadcastRangeForSubscriber(
eq(0),
@@ -402,21 +404,21 @@ public class CellBroadcastConfigServiceTest extends CellBroadcastTest {
eq(SmsManager.CELL_BROADCAST_RAN_TYPE_GSM));
setPreference(CellBroadcastSettings.KEY_ENABLE_CMAS_EXTREME_THREAT_ALERTS, true);
- mConfigService.setCellBroadcastOnSub(mSmsManager, false);
+ mConfigService.enableCellBroadcastChannels(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
- verify(mMockedSmsService, times(2)).disableCellBroadcastRangeForSubscriber(
+ verify(mMockedSmsService, times(2)).enableCellBroadcastRangeForSubscriber(
eq(0),
eq(SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT),
eq(SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT),
eq(SmsManager.CELL_BROADCAST_RAN_TYPE_CDMA));
- verify(mMockedSmsService, times(2)).disableCellBroadcastRangeForSubscriber(
+ verify(mMockedSmsService, times(2)).enableCellBroadcastRangeForSubscriber(
eq(0),
eq(SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED),
eq(SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY),
eq(SmsManager.CELL_BROADCAST_RAN_TYPE_GSM));
- verify(mMockedSmsService, times(2)).disableCellBroadcastRangeForSubscriber(
+ verify(mMockedSmsService, times(2)).enableCellBroadcastRangeForSubscriber(
eq(0),
eq(SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE),
eq(SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE),
@@ -432,7 +434,7 @@ public class CellBroadcastConfigServiceTest extends CellBroadcastTest {
public void testEnablingSevere() throws Exception {
setPreference(CellBroadcastSettings.KEY_ENABLE_ALERTS_MASTER_TOGGLE, true);
setPreference(CellBroadcastSettings.KEY_ENABLE_CMAS_SEVERE_THREAT_ALERTS, true);
- mConfigService.setCellBroadcastOnSub(mSmsManager, true);
+ mConfigService.enableCellBroadcastChannels(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
verify(mMockedSmsService, times(1)).enableCellBroadcastRangeForSubscriber(
eq(0),
@@ -453,7 +455,7 @@ public class CellBroadcastConfigServiceTest extends CellBroadcastTest {
eq(SmsManager.CELL_BROADCAST_RAN_TYPE_GSM));
setPreference(CellBroadcastSettings.KEY_ENABLE_CMAS_SEVERE_THREAT_ALERTS, false);
- mConfigService.setCellBroadcastOnSub(mSmsManager, true);
+ mConfigService.enableCellBroadcastChannels(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
verify(mMockedSmsService, times(1)).disableCellBroadcastRangeForSubscriber(
eq(0),
@@ -474,21 +476,21 @@ public class CellBroadcastConfigServiceTest extends CellBroadcastTest {
eq(SmsManager.CELL_BROADCAST_RAN_TYPE_GSM));
setPreference(CellBroadcastSettings.KEY_ENABLE_CMAS_SEVERE_THREAT_ALERTS, true);
- mConfigService.setCellBroadcastOnSub(mSmsManager, false);
+ mConfigService.enableCellBroadcastChannels(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
- verify(mMockedSmsService, times(2)).disableCellBroadcastRangeForSubscriber(
+ verify(mMockedSmsService, times(2)).enableCellBroadcastRangeForSubscriber(
eq(0),
eq(SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT),
eq(SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT),
eq(SmsManager.CELL_BROADCAST_RAN_TYPE_CDMA));
- verify(mMockedSmsService, times(2)).disableCellBroadcastRangeForSubscriber(
+ verify(mMockedSmsService, times(2)).enableCellBroadcastRangeForSubscriber(
eq(0),
eq(SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED),
eq(SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY),
eq(SmsManager.CELL_BROADCAST_RAN_TYPE_GSM));
- verify(mMockedSmsService, times(2)).disableCellBroadcastRangeForSubscriber(
+ verify(mMockedSmsService, times(2)).enableCellBroadcastRangeForSubscriber(
eq(0),
eq(SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE),
eq(SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE),
@@ -503,7 +505,7 @@ public class CellBroadcastConfigServiceTest extends CellBroadcastTest {
public void testEnablingAmber() throws Exception {
setPreference(CellBroadcastSettings.KEY_ENABLE_ALERTS_MASTER_TOGGLE, true);
setPreference(CellBroadcastSettings.KEY_ENABLE_CMAS_AMBER_ALERTS, true);
- mConfigService.setCellBroadcastOnSub(mSmsManager, true);
+ mConfigService.enableCellBroadcastChannels(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
verify(mMockedSmsService, times(1)).enableCellBroadcastRangeForSubscriber(
eq(0),
@@ -524,7 +526,7 @@ public class CellBroadcastConfigServiceTest extends CellBroadcastTest {
eq(SmsManager.CELL_BROADCAST_RAN_TYPE_GSM));
setPreference(CellBroadcastSettings.KEY_ENABLE_CMAS_AMBER_ALERTS, false);
- mConfigService.setCellBroadcastOnSub(mSmsManager, true);
+ mConfigService.enableCellBroadcastChannels(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
verify(mMockedSmsService, times(1)).disableCellBroadcastRangeForSubscriber(
eq(0),
@@ -545,21 +547,21 @@ public class CellBroadcastConfigServiceTest extends CellBroadcastTest {
eq(SmsManager.CELL_BROADCAST_RAN_TYPE_GSM));
setPreference(CellBroadcastSettings.KEY_ENABLE_CMAS_AMBER_ALERTS, true);
- mConfigService.setCellBroadcastOnSub(mSmsManager, false);
+ mConfigService.enableCellBroadcastChannels(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
- verify(mMockedSmsService, times(2)).disableCellBroadcastRangeForSubscriber(
+ verify(mMockedSmsService, times(2)).enableCellBroadcastRangeForSubscriber(
eq(0),
eq(SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY),
eq(SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY),
eq(SmsManager.CELL_BROADCAST_RAN_TYPE_CDMA));
- verify(mMockedSmsService, times(2)).disableCellBroadcastRangeForSubscriber(
+ verify(mMockedSmsService, times(2)).enableCellBroadcastRangeForSubscriber(
eq(0),
eq(SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY),
eq(SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY),
eq(SmsManager.CELL_BROADCAST_RAN_TYPE_GSM));
- verify(mMockedSmsService, times(2)).disableCellBroadcastRangeForSubscriber(
+ verify(mMockedSmsService, times(2)).enableCellBroadcastRangeForSubscriber(
eq(0),
eq(SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY_LANGUAGE),
eq(SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY_LANGUAGE),
@@ -573,7 +575,7 @@ public class CellBroadcastConfigServiceTest extends CellBroadcastTest {
@SmallTest
public void testEnablingETWS() throws Exception {
setPreference(CellBroadcastSettings.KEY_ENABLE_ALERTS_MASTER_TOGGLE, true);
- mConfigService.setCellBroadcastOnSub(mSmsManager, true);
+ mConfigService.enableCellBroadcastChannels(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
verify(mMockedSmsService, times(1)).enableCellBroadcastRangeForSubscriber(
eq(0),
@@ -588,7 +590,7 @@ public class CellBroadcastConfigServiceTest extends CellBroadcastTest {
eq(SmsManager.CELL_BROADCAST_RAN_TYPE_GSM));
setPreference(CellBroadcastSettings.KEY_ENABLE_ALERTS_MASTER_TOGGLE, false);
- mConfigService.setCellBroadcastOnSub(mSmsManager, true);
+ mConfigService.enableCellBroadcastChannels(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
verify(mMockedSmsService, times(1)).disableCellBroadcastRangeForSubscriber(
eq(0),
@@ -603,15 +605,15 @@ public class CellBroadcastConfigServiceTest extends CellBroadcastTest {
eq(SmsManager.CELL_BROADCAST_RAN_TYPE_GSM));
setPreference(CellBroadcastSettings.KEY_ENABLE_ALERTS_MASTER_TOGGLE, true);
- mConfigService.setCellBroadcastOnSub(mSmsManager, false);
+ mConfigService.enableCellBroadcastChannels(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
- verify(mMockedSmsService, times(2)).disableCellBroadcastRangeForSubscriber(
+ verify(mMockedSmsService, times(2)).enableCellBroadcastRangeForSubscriber(
eq(0),
eq(SmsCbConstants.MESSAGE_ID_ETWS_EARTHQUAKE_WARNING),
eq(SmsCbConstants.MESSAGE_ID_ETWS_EARTHQUAKE_AND_TSUNAMI_WARNING),
eq(SmsManager.CELL_BROADCAST_RAN_TYPE_GSM));
- verify(mMockedSmsService, times(2)).disableCellBroadcastRangeForSubscriber(
+ verify(mMockedSmsService, times(2)).enableCellBroadcastRangeForSubscriber(
eq(0),
eq(SmsCbConstants.MESSAGE_ID_ETWS_OTHER_EMERGENCY_TYPE),
eq(SmsCbConstants.MESSAGE_ID_ETWS_OTHER_EMERGENCY_TYPE),
diff --git a/tests/unit/src/com/android/cellbroadcastreceiver/CellBroadcastServiceTestCase.java b/tests/unit/src/com/android/cellbroadcastreceiver/CellBroadcastServiceTestCase.java
index c29dff499..158c84f0f 100644
--- a/tests/unit/src/com/android/cellbroadcastreceiver/CellBroadcastServiceTestCase.java
+++ b/tests/unit/src/com/android/cellbroadcastreceiver/CellBroadcastServiceTestCase.java
@@ -17,6 +17,7 @@
package com.android.cellbroadcastreceiver;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doReturn;
@@ -108,10 +109,13 @@ public abstract class CellBroadcastServiceTestCase<T extends Service> extends Se
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
// A hack to return mResources from static method
- // CellBroadcastSettings.getResourcesForDefaultSmsSubscriptionId(context).
+ // CellBroadcastSettings.getResources(context).
doReturn(mSubService).when(mSubService).queryLocalInterface(anyString());
doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID).when(mSubService).getDefaultSubId();
doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID).when(mSubService).getDefaultSmsSubId();
+
+ doReturn(new String[]{""}).when(mResources).getStringArray(anyInt());
+
mMockedServiceManager = new MockedServiceManager();
mMockedServiceManager.replaceService("isub", mSubService);
mContext = new TestContextWrapper(getContext());
diff --git a/tests/unit/src/com/android/cellbroadcastreceiver/CellBroadcastTest.java b/tests/unit/src/com/android/cellbroadcastreceiver/CellBroadcastTest.java
index c97e6382f..da8e2f3a4 100644
--- a/tests/unit/src/com/android/cellbroadcastreceiver/CellBroadcastTest.java
+++ b/tests/unit/src/com/android/cellbroadcastreceiver/CellBroadcastTest.java
@@ -16,6 +16,7 @@
package com.android.cellbroadcastreceiver;
+import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
@@ -54,7 +55,7 @@ public abstract class CellBroadcastTest {
TAG = tag;
MockitoAnnotations.initMocks(this);
// A hack to return mResources from static method
- // CellBroadcastSettings.getResourcesForDefaultSmsSubscriptionId(context).
+ // CellBroadcastSettings.getResources(context).
doReturn(mSubService).when(mSubService).queryLocalInterface(anyString());
doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID).when(mSubService).getDefaultSubId();
doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID).when(mSubService).getDefaultSmsSubId();
@@ -67,6 +68,8 @@ public abstract class CellBroadcastTest {
doReturn(mCarrierConfigManager).when(mContext)
.getSystemService(eq(Context.CARRIER_CONFIG_SERVICE));
doReturn(mResources).when(mContext).getResources();
+ doReturn(mContext).when(mContext).getApplicationContext();
+ doReturn(new String[]{""}).when(mResources).getStringArray(anyInt());
}
void carrierConfigSetStringArray(int subId, String key, String[] values) {