[automerger skipped] Merge "Update CellBroadcastIntents API" am: 6ec1a35516 am: 078f736139 am: 76aa0c5506 -s ours
am skip reason: Change-Id I33cdf12beee2dc57030c0c9b6268ca5d1a3a2228 with SHA-1 d05f41e9a9 is in history

Change-Id: Ib6261726119f6f86f63a38c381c59f0cd6f30be7
diff --git a/Android.bp b/Android.bp
index 20746b7..d423fa2 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,30 +1,68 @@
 // Copyright 2019 The Android Open Source Project
-
-android_app {
-    name: "CellBroadcastServiceModule",
+java_defaults {
+    name: "CellBroadcastServiceCommon",
+    min_sdk_version: "29",
     srcs: [
       "src/**/*.java",
       ":framework-annotations",
       ":framework-cellbroadcast-shared-srcs",
+      ":statslog-cellbroadcast-java-gen",
     ],
     libs: [
-        "telephony-common",
         "unsupportedappusage",
     ],
-    platform_apis: true,
+    sdk_version: "system_current",
     certificate: "platform",
     privileged: true,
     resource_dirs: ["res"],
     optimize: {
         proguard_flags_files: ["proguard.flags"],
     },
+    jarjar_rules: "cellbroadcast-jarjar-rules.txt",
+    plugins: ["java_api_finder"],
 }
 
+android_app {
+    name: "CellBroadcastServiceModule",
+    defaults: ["CellBroadcastServiceCommon"],
+    certificate: "networkstack",
+    manifest: "AndroidManifest.xml",
+}
+
+android_app {
+     name: "CellBroadcastServiceModulePlatform",
+     defaults: ["CellBroadcastServiceCommon"],
+     certificate: "platform",
+     // CellBroadcastServicePlatformModule is a replacement for com.android.cellbroadcast apex
+     // which consists of CellBroadcastServiceModule
+     overrides: ["com.android.cellbroadcast"],
+     manifest: "AndroidManifest_Platform.xml",
+}
+
+genrule {
+  name: "statslog-cellbroadcast-java-gen",
+  tools: ["stats-log-api-gen"],
+  cmd: "$(location stats-log-api-gen) --java $(out) --module cellbroadcast --javaPackage com.android.cellbroadcastservice --javaClass CellBroadcastStatsLog",
+  out: ["com/android/cellbroadcastservice/CellBroadcastStatsLog.java"],
+}
+
+
 // used to share common constants between cellbroadcastservice and cellbroadcastreceier
 filegroup {
     name: "cellbroadcast-constants-shared-srcs",
     srcs: [
         "src/com/android/cellbroadcastservice/SmsCbConstants.java",
+        ":statslog-cellbroadcast-java-gen",
+    ],
+}
+
+// used to share src with cellbroadcastreceiver test app
+filegroup {
+    name: "cellbroadcast-util-shared-srcs",
+    srcs: [
+        "src/com/android/cellbroadcastservice/GsmAlphabet.java",
+        "src/com/android/cellbroadcastservice/CbGeoUtils.java",
+        "src/com/android/cellbroadcastservice/SmsCbHeader.java",
     ],
 }
 
@@ -34,5 +72,6 @@
     srcs: [
         "src/**/*.java",
         ":framework-cellbroadcast-shared-srcs",
+        ":statslog-cellbroadcast-java-gen",
     ],
 }
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index e075727..a63b802 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -17,7 +17,9 @@
  */
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        android:sharedUserId="android.uid.phone"
+        android:sharedUserId="android.uid.networkstack"
+        android:versionCode="300000000"
+        android:versionName="R-initial"
         package="com.android.cellbroadcastservice">
 
     <original-package android:name="com.android.cellbroadcastservice" />
@@ -29,15 +31,19 @@
     <uses-permission android:name="android.permission.READ_CELL_BROADCASTS" />
     <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
     <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission android:name="android.permission.BROADCAST_SMS" />
+    <uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS" />
 
     <uses-sdk android:minSdkVersion="29"/>
 
     <application android:label="Module used to handle cell broadcasts."
             android:defaultToDeviceProtectedStorage="true"
             android:directBootAware="true"
-            android:process="com.android.phone">
+            android:persistent="true"
+            android:process="com.android.networkstack.process">
 
         <service android:name="DefaultCellBroadcastService"
+                android:process="com.android.networkstack.process"
                 android:exported="true"
                 android:permission="android.permission.BIND_CELL_BROADCAST_SERVICE">
             <intent-filter>
diff --git a/AndroidManifest_Platform.xml b/AndroidManifest_Platform.xml
new file mode 100644
index 0000000..067741f
--- /dev/null
+++ b/AndroidManifest_Platform.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * 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.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    android:sharedUserId="android.uid.phone"
+    package="com.android.cellbroadcastservice">
+
+    <original-package android:name="com.android.cellbroadcastservice" />
+
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.RECEIVE_SMS" />
+    <uses-permission android:name="android.permission.RECEIVE_EMERGENCY_BROADCAST" />
+    <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
+    <uses-permission android:name="android.permission.READ_CELL_BROADCASTS" />
+    <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission android:name="android.permission.BROADCAST_SMS" />
+    <uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS" />
+
+    <uses-sdk android:minSdkVersion="29"/>
+
+    <application android:label="Module used to handle cell broadcasts."
+        android:defaultToDeviceProtectedStorage="true"
+        android:persistent="true"
+        android:directBootAware="true"
+        android:process="com.android.phone">
+
+        <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>
+    </application>
+</manifest>
diff --git a/CleanSpec.mk b/CleanSpec.mk
new file mode 100644
index 0000000..f69421d
--- /dev/null
+++ b/CleanSpec.mk
@@ -0,0 +1,50 @@
+# Copyright (C) 2007 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# If you don't need to do a full clean build but would like to touch
+# a file or delete some intermediate files, add a clean step to the end
+# of the list.  These steps will only be run once, if they haven't been
+# run before.
+#
+# E.g.:
+#     $(call add-clean-step, touch -c external/sqlite/sqlite3.h)
+#     $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates)
+#
+# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with
+# files that are missing or have been moved.
+#
+# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory.
+# Use $(OUT_DIR) to refer to the "out" directory.
+#
+# If you need to re-do something that's already mentioned, just copy
+# the command and add it to the bottom of the list.  E.g., if a change
+# that you made last week required touching a file and a change you
+# made today requires touching the same file, just copy the old
+# touch step and add it to the end of the list.
+#
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
+
+# For example:
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates)
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates)
+#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
+#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
+
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/CellBroadcastServiceModule)
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
diff --git a/OWNERS b/OWNERS
index 9a61858..3059d4d 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,7 +1,15 @@
 amitmahajan@google.com
+breadley@google.com
 fionaxu@google.com
 jackyu@google.com
+hallliu@google.com
 rgreenwalt@google.com
-refuhoo@google.com
+tgunn@google.com
 jminjie@google.com
 shuoq@google.com
+refuhoo@google.com
+nazaninb@google.com
+sarahchin@google.com
+dbright@google.com
+xiaotonj@google.com
+
diff --git a/cellbroadcast-jarjar-rules.txt b/cellbroadcast-jarjar-rules.txt
index 32cfb51..3e4127a 100644
--- a/cellbroadcast-jarjar-rules.txt
+++ b/cellbroadcast-jarjar-rules.txt
@@ -1,6 +1,5 @@
 rule android.os.HandlerExecutor* com.android.cellbroadcastservice.HandlerExecutor@1
 rule android.util.LocalLog* com.android.cellbroadcastservice.LocalLog@1
-rule android.util.Slog* com.android.cellbroadcastservice.Slog@1
 rule android.internal.util.IState* com.android.cellbroadcastservice.IState@1
 rule android.internal.util.Preconditions* com.android.cellbroadcastservice.Preconditions@1
 rule android.internal.util.State* com.android.cellbroadcastservice.State@1
diff --git a/src/com/android/cellbroadcastservice/BearerData.java b/src/com/android/cellbroadcastservice/BearerData.java
index 032266a..c634527 100644
--- a/src/com/android/cellbroadcastservice/BearerData.java
+++ b/src/com/android/cellbroadcastservice/BearerData.java
@@ -576,65 +576,57 @@
      * @param serviceCategory the envelope service category (for CMAS alert handling)
      * @return an instance of BearerData.
      */
-    public static BearerData decode(Context context, byte[] smsData, int serviceCategory) {
-        try {
-            BitwiseInputStream inStream = new BitwiseInputStream(smsData);
-            BearerData bData = new BearerData();
-            int foundSubparamMask = 0;
-            while (inStream.available() > 0) {
-                int subparamId = inStream.read(8);
-                int subparamIdBit = 1 << subparamId;
-                // int is 4 bytes. This duplicate check has a limit to Id number up to 32 (4*8)
-                // as 32th bit is the max bit in int.
-                // Per 3GPP2 C.S0015-B Table 4.5-1 Bearer Data Subparameter Identifiers:
-                // last defined subparam ID is 23 (00010111 = 0x17 = 23).
-                // Only do duplicate subparam ID check if subparam is within defined value as
-                // reserved subparams are just skipped.
-                if ((foundSubparamMask & subparamIdBit) != 0 &&
-                        (subparamId >= SUBPARAM_MESSAGE_IDENTIFIER &&
-                                subparamId <= SUBPARAM_ID_LAST_DEFINED)) {
-                    throw new CodingException("illegal duplicate subparameter (" +
-                            subparamId + ")");
-                }
-                boolean decodeSuccess;
-                switch (subparamId) {
-                    case SUBPARAM_MESSAGE_IDENTIFIER:
-                        decodeSuccess = decodeMessageId(bData, inStream);
-                        break;
-                    case SUBPARAM_USER_DATA:
-                        decodeSuccess = decodeUserData(bData, inStream);
-                        break;
-                    case SUBPARAM_LANGUAGE_INDICATOR:
-                        decodeSuccess = decodeLanguageIndicator(bData, inStream);
-                        break;
-                    case SUBPARAM_PRIORITY_INDICATOR:
-                        decodeSuccess = decodePriorityIndicator(bData, inStream);
-                        break;
-                    default:
-                        decodeSuccess = decodeReserved(inStream, subparamId);
-                }
-                if (decodeSuccess &&
-                        (subparamId >= SUBPARAM_MESSAGE_IDENTIFIER &&
-                                subparamId <= SUBPARAM_ID_LAST_DEFINED)) {
-                    foundSubparamMask |= subparamIdBit;
-                }
+    public static BearerData decode(Context context, byte[] smsData, int serviceCategory)
+            throws CodingException, BitwiseInputStream.AccessException {
+        BitwiseInputStream inStream = new BitwiseInputStream(smsData);
+        BearerData bData = new BearerData();
+        int foundSubparamMask = 0;
+        while (inStream.available() > 0) {
+            int subparamId = inStream.read(8);
+            int subparamIdBit = 1 << subparamId;
+            // int is 4 bytes. This duplicate check has a limit to Id number up to 32 (4*8)
+            // as 32th bit is the max bit in int.
+            // Per 3GPP2 C.S0015-B Table 4.5-1 Bearer Data Subparameter Identifiers:
+            // last defined subparam ID is 23 (00010111 = 0x17 = 23).
+            // Only do duplicate subparam ID check if subparam is within defined value as
+            // reserved subparams are just skipped.
+            if ((foundSubparamMask & subparamIdBit) != 0 && (
+                    subparamId >= SUBPARAM_MESSAGE_IDENTIFIER
+                            && subparamId <= SUBPARAM_ID_LAST_DEFINED)) {
+                throw new CodingException("illegal duplicate subparameter (" + subparamId + ")");
             }
-            if ((foundSubparamMask & (1 << SUBPARAM_MESSAGE_IDENTIFIER)) == 0) {
-                throw new CodingException("missing MESSAGE_IDENTIFIER subparam");
+            boolean decodeSuccess;
+            switch (subparamId) {
+                case SUBPARAM_MESSAGE_IDENTIFIER:
+                    decodeSuccess = decodeMessageId(bData, inStream);
+                    break;
+                case SUBPARAM_USER_DATA:
+                    decodeSuccess = decodeUserData(bData, inStream);
+                    break;
+                case SUBPARAM_LANGUAGE_INDICATOR:
+                    decodeSuccess = decodeLanguageIndicator(bData, inStream);
+                    break;
+                case SUBPARAM_PRIORITY_INDICATOR:
+                    decodeSuccess = decodePriorityIndicator(bData, inStream);
+                    break;
+                default:
+                    decodeSuccess = decodeReserved(inStream, subparamId);
             }
-            if (bData.userData != null) {
-                if (isCmasAlertCategory(serviceCategory)) {
-                    decodeCmasUserData(context, bData, serviceCategory);
-                } else {
-                    decodeUserDataPayload(context, bData.userData, bData.hasUserDataHeader);
-                }
+            if (decodeSuccess && (subparamId >= SUBPARAM_MESSAGE_IDENTIFIER
+                    && subparamId <= SUBPARAM_ID_LAST_DEFINED)) {
+                foundSubparamMask |= subparamIdBit;
             }
-            return bData;
-        } catch (BitwiseInputStream.AccessException ex) {
-            Log.e(LOG_TAG, "BearerData decode failed: " + ex);
-        } catch (CodingException ex) {
-            Log.e(LOG_TAG, "BearerData decode failed: " + ex);
         }
-        return null;
+        if ((foundSubparamMask & (1 << SUBPARAM_MESSAGE_IDENTIFIER)) == 0) {
+            throw new CodingException("missing MESSAGE_IDENTIFIER subparam");
+        }
+        if (bData.userData != null) {
+            if (isCmasAlertCategory(serviceCategory)) {
+                decodeCmasUserData(context, bData, serviceCategory);
+            } else {
+                decodeUserDataPayload(context, bData.userData, bData.hasUserDataHeader);
+            }
+        }
+        return bData;
     }
 }
diff --git a/src/com/android/cellbroadcastservice/CbGeoUtils.java b/src/com/android/cellbroadcastservice/CbGeoUtils.java
index 0a8fe48..044ecea 100644
--- a/src/com/android/cellbroadcastservice/CbGeoUtils.java
+++ b/src/com/android/cellbroadcastservice/CbGeoUtils.java
@@ -232,6 +232,9 @@
                     break;
                 default:
                     Log.e(TAG, "Invalid geometry format " + geometryStr);
+                    CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR,
+                            CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_ERROR__TYPE__UNEXPECTED_GEOMETRY_FROM_FWK,
+                            geometryStr);
             }
         }
         return geometries;
diff --git a/src/com/android/cellbroadcastservice/CdmaServiceCategoryProgramHandler.java b/src/com/android/cellbroadcastservice/CdmaServiceCategoryProgramHandler.java
index 032edcc..2050960 100644
--- a/src/com/android/cellbroadcastservice/CdmaServiceCategoryProgramHandler.java
+++ b/src/com/android/cellbroadcastservice/CdmaServiceCategoryProgramHandler.java
@@ -16,6 +16,9 @@
 
 package com.android.cellbroadcastservice;
 
+import static com.android.cellbroadcastservice.CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_ERROR__TYPE__CDMA_SCP_HANDLING_ERROR;
+import static com.android.cellbroadcastservice.CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_ERROR__TYPE__UNEXPECTED_CDMA_SCP_MESSAGE_TYPE_FROM_FWK;
+
 import android.Manifest;
 import android.app.Activity;
 import android.app.AppOpsManager;
@@ -111,6 +114,9 @@
                     cdmaScpMessage.mCallback);
         } else {
             loge("handleMessage got object of type: " + message.obj.getClass().getName());
+            CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR,
+                    CELL_BROADCAST_MESSAGE_ERROR__TYPE__UNEXPECTED_CDMA_SCP_MESSAGE_TYPE_FROM_FWK,
+                    message.obj.getClass().getName());
             return false;
         }
     }
@@ -128,6 +134,8 @@
             String originatingAddress, int phoneId, Consumer<Bundle> callback) {
         if (programData == null) {
             loge("handleServiceCategoryProgramData: program data list is null!");
+            CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR,
+                    CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_ERROR__TYPE__CDMA_SCP_EMPTY);
             return false;
         }
 
@@ -143,7 +151,7 @@
         for (String pkg : pkgs) {
             intent.setPackage(pkg);
             mContext.sendOrderedBroadcast(intent, Manifest.permission.RECEIVE_SMS,
-                    AppOpsManager.OP_RECEIVE_SMS, mScpResultsReceiver,
+                    AppOpsManager.OPSTR_RECEIVE_SMS, mScpResultsReceiver,
                     getHandler(), Activity.RESULT_OK, null, null);
             mScpCallback.add(callback);
         }
@@ -160,6 +168,9 @@
             int resultCode = getResultCode();
             if ((resultCode != Activity.RESULT_OK) && (resultCode != Intents.RESULT_SMS_HANDLED)) {
                 loge("SCP results error: result code = " + resultCode);
+                CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR,
+                        CELL_BROADCAST_MESSAGE_ERROR__TYPE__CDMA_SCP_HANDLING_ERROR,
+                        "result code = " + resultCode);
                 return;
             }
             Bundle extras = getResultExtras(false);
diff --git a/src/com/android/cellbroadcastservice/CellBroadcastHandler.java b/src/com/android/cellbroadcastservice/CellBroadcastHandler.java
index e8e19c1..a0076a8 100644
--- a/src/com/android/cellbroadcastservice/CellBroadcastHandler.java
+++ b/src/com/android/cellbroadcastservice/CellBroadcastHandler.java
@@ -16,6 +16,11 @@
 
 package com.android.cellbroadcastservice;
 
+import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+
+import static com.android.cellbroadcastservice.CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_ERROR__TYPE__UNEXPECTED_CDMA_MESSAGE_TYPE_FROM_FWK;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -30,11 +35,12 @@
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.location.Location;
-import android.location.LocationListener;
 import android.location.LocationManager;
+import android.location.LocationRequest;
 import android.net.Uri;
-import android.os.Bundle;
+import android.os.CancellationSignal;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.Looper;
 import android.os.Message;
 import android.os.Process;
@@ -50,7 +56,6 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.cdma.CdmaSmsCbProgramData;
 import android.text.TextUtils;
-import android.text.format.DateUtils;
 import android.util.LocalLog;
 import android.util.Log;
 
@@ -65,6 +70,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -97,7 +103,7 @@
     private static final boolean IS_DEBUGGABLE = SystemProperties.getInt("ro.debuggable", 0) == 1;
 
     /** Uses to request the location update. */
-    public final LocationRequester mLocationRequester;
+    private final LocationRequester mLocationRequester;
 
     /** Timestamp of last airplane mode on */
     protected long mLastAirplaneModeTime = 0;
@@ -114,6 +120,25 @@
      */
     private final Map<Integer, Integer> mServiceCategoryCrossRATMap;
 
+    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            switch (intent.getAction()) {
+                case Intent.ACTION_AIRPLANE_MODE_CHANGED:
+                    boolean airplaneModeOn = intent.getBooleanExtra("state", false);
+                    if (airplaneModeOn) {
+                        mLastAirplaneModeTime = System.currentTimeMillis();
+                        log("Airplane mode on.");
+                    }
+                    break;
+                case ACTION_DUPLICATE_DETECTION:
+                    mEnableDuplicateDetection = intent.getBooleanExtra(EXTRA_ENABLE,
+                            true);
+                    break;
+            }
+        }
+    };
+
     private CellBroadcastHandler(Context context) {
         this("CellBroadcastHandler", context, Looper.myLooper());
     }
@@ -124,7 +149,7 @@
         mLocationRequester = new LocationRequester(
                 context,
                 (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE),
-                getHandler().getLooper());
+                getHandler());
 
         // Adding GSM / CDMA service category mapping.
         mServiceCategoryCrossRATMap = Stream.of(new Integer[][] {
@@ -188,26 +213,13 @@
         if (IS_DEBUGGABLE) {
             intentFilter.addAction(ACTION_DUPLICATE_DETECTION);
         }
-        mContext.registerReceiver(
-                new BroadcastReceiver() {
-                    @Override
-                    public void onReceive(Context context, Intent intent) {
-                        switch (intent.getAction()) {
-                            case Intent.ACTION_AIRPLANE_MODE_CHANGED:
-                                boolean airplaneModeOn = intent.getBooleanExtra("state", false);
-                                if (airplaneModeOn) {
-                                    mLastAirplaneModeTime = System.currentTimeMillis();
-                                    log("Airplane mode on.");
-                                }
-                                break;
-                            case ACTION_DUPLICATE_DETECTION:
-                                mEnableDuplicateDetection = intent.getBooleanExtra(EXTRA_ENABLE,
-                                        true);
-                                break;
-                        }
 
-                    }
-                }, intentFilter);
+        mContext.registerReceiver(mReceiver, intentFilter);
+    }
+
+    public void cleanup() {
+        if (DBG) log("CellBroadcastHandler cleanup");
+        mContext.unregisterReceiver(mReceiver);
     }
 
     /**
@@ -237,7 +249,10 @@
             }
             return false;
         } else {
-            loge("handleMessage got object of type: " + message.obj.getClass().getName());
+            loge("handleSmsMessage got object of type: " + message.obj.getClass().getName());
+            CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR,
+                    CELL_BROADCAST_MESSAGE_ERROR__TYPE__UNEXPECTED_CDMA_MESSAGE_TYPE_FROM_FWK,
+                    message.obj.getClass().getName());
             return false;
         }
     }
@@ -347,7 +362,6 @@
                 // Check serial number if message is from the same carrier.
                 if (message.getSerialNumber() != messageToCheck.getSerialNumber()) {
                     // Not a dup. Check next one.
-                    log("Serial number check. Not a dup. " + messageToCheck);
                     continue;
                 }
 
@@ -356,7 +370,6 @@
                         && message.getEtwsWarningInfo().isPrimary()
                         != messageToCheck.getEtwsWarningInfo().isPrimary()) {
                     // Not a dup. Check next one.
-                    log("ETWS primary check. Not a dup. " + messageToCheck);
                     continue;
                 }
 
@@ -370,7 +383,6 @@
                                 messageToCheck.getServiceCategory()),
                         message.getServiceCategory())) {
                     // Not a dup. Check next one.
-                    log("Service category check. Not a dup. " + messageToCheck);
                     continue;
                 }
 
@@ -580,18 +592,9 @@
 
         /**
          * 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.
+         * Most of the location request should be responded within 30 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;
+        private static final int DEFAULT_MAXIMUM_WAIT_TIME_SEC = 30;
 
         /**
          * Request location update from network or gps location provider. Network provider will be
@@ -601,17 +604,20 @@
                 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;
+        private final Handler mLocationHandler;
 
-        LocationRequester(Context context, LocationManager locationManager, Looper looper) {
+        private int mNumLocationUpdatesInProgress;
+
+        private final List<CancellationSignal> mCancellationSignals = new ArrayList<>();
+
+        LocationRequester(Context context, LocationManager locationManager, Handler handler) {
             mLocationManager = locationManager;
-            mLooper = looper;
             mCallbacks = new ArrayList<>();
             mContext = context;
-            mLocationHandler = new LocationHandler(looper);
+            mLocationHandler = handler;
+            mNumLocationUpdatesInProgress = 0;
         }
 
         /**
@@ -619,103 +625,83 @@
          * {@code null} location will be called immediately.
          *
          * @param callback a callback to the response when the location is available
-         * @param maximumWaitTimeSec the maximum wait time of this request. If location is not
+         * @param maximumWaitTimeS 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();
+                int maximumWaitTimeS) {
+            mLocationHandler.post(() -> requestLocationUpdateInternal(callback, maximumWaitTimeS));
         }
 
-        private void onLocationUpdate(@Nullable LatLng location) {
+        private void onLocationUpdate(@Nullable Location location) {
+            mNumLocationUpdatesInProgress--;
+
+            LatLng latLng = null;
+            if (location != null) {
+                Log.d(TAG, "Got location update");
+                latLng = new LatLng(location.getLatitude(), location.getLongitude());
+            } else if (mNumLocationUpdatesInProgress > 0) {
+                Log.d(TAG, "Still waiting for " + mNumLocationUpdatesInProgress
+                        + " more location updates.");
+                return;
+            } else {
+                Log.d(TAG, "Location is not available.");
+            }
+
             for (LocationUpdateCallback callback : mCallbacks) {
-                callback.onLocationUpdate(location);
+                callback.onLocationUpdate(latLng);
             }
             mCallbacks.clear();
+
+            mCancellationSignals.forEach(CancellationSignal::cancel);
+            mCancellationSignals.clear();
+
+            mNumLocationUpdatesInProgress = 0;
         }
 
         private void requestLocationUpdateInternal(@NonNull LocationUpdateCallback callback,
-                int maximumWaitTimeSec) {
+                int maximumWaitTimeS) {
             if (DBG) Log.d(TAG, "requestLocationUpdate");
-            if (!isLocationServiceAvailable()) {
+            if (!hasPermission(ACCESS_FINE_LOCATION) && !hasPermission(ACCESS_COARSE_LOCATION)) {
                 if (DBG) {
                     Log.d(TAG, "Can't request location update because of no location permission");
                 }
                 callback.onLocationUpdate(null);
                 return;
             }
+            if (mNumLocationUpdatesInProgress == 0) {
+                for (String provider : LOCATION_PROVIDERS) {
+                    if (!mLocationManager.isProviderEnabled(provider)) {
+                        if (DBG) {
+                            Log.d(TAG, "provider " + provider + " not available");
+                        }
+                        continue;
+                    }
+                    LocationRequest request = LocationRequest.createFromDeprecatedProvider(provider,
+                            0, 0, true);
+                    if (maximumWaitTimeS == SmsCbMessage.MAXIMUM_WAIT_TIME_NOT_SET) {
+                        maximumWaitTimeS = DEFAULT_MAXIMUM_WAIT_TIME_SEC;
+                    }
+                    request.setExpireIn(TimeUnit.SECONDS.toMillis(maximumWaitTimeS));
 
-            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;
+                    CancellationSignal signal = new CancellationSignal();
+                    mCancellationSignals.add(signal);
+                    mLocationManager.getCurrentLocation(request, signal,
+                            new HandlerExecutor(mLocationHandler), this::onLocationUpdate);
+                    mNumLocationUpdatesInProgress++;
                 }
             }
-        }
-
-        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;
+            if (mNumLocationUpdatesInProgress > 0) {
+                mCallbacks.add(callback);
+            } else {
+                callback.onLocationUpdate(null);
             }
-            return false;
         }
 
         private boolean hasPermission(String permission) {
             return mContext.checkPermission(permission, Process.myPid(), Process.myUid())
                     == PackageManager.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/cellbroadcastservice/CellBroadcastProvider.java b/src/com/android/cellbroadcastservice/CellBroadcastProvider.java
index 2b3fc63..856a0f2 100644
--- a/src/com/android/cellbroadcastservice/CellBroadcastProvider.java
+++ b/src/com/android/cellbroadcastservice/CellBroadcastProvider.java
@@ -16,6 +16,8 @@
 
 package com.android.cellbroadcastservice;
 
+import static com.android.cellbroadcastservice.CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_ERROR__TYPE__FAILED_TO_INSERT_TO_DB;
+
 import android.content.ContentProvider;
 import android.content.ContentUris;
 import android.content.ContentValues;
@@ -27,8 +29,6 @@
 import android.database.sqlite.SQLiteOpenHelper;
 import android.database.sqlite.SQLiteQueryBuilder;
 import android.net.Uri;
-import android.os.Binder;
-import android.os.Process;
 import android.provider.Telephony.CellBroadcasts;
 import android.text.TextUtils;
 import android.util.Log;
@@ -104,7 +104,7 @@
     public static final String[] QUERY_COLUMNS = {
             CellBroadcasts._ID,
             CellBroadcasts.SLOT_INDEX,
-            CellBroadcasts.SUB_ID,
+            CellBroadcasts.SUBSCRIPTION_ID,
             CellBroadcasts.GEOGRAPHICAL_SCOPE,
             CellBroadcasts.PLMN,
             CellBroadcasts.LAC,
@@ -232,12 +232,27 @@
                             .notifyChange(CONTENT_URI, null /* observer */);
                     return newUri;
                 } else {
-                    Log.e(TAG, "Insert record failed because of unknown reason, uri = " + uri);
+                    String errorString = "uri=" + uri.toString() + " values=" + values;
+                    // 1000 character limit for error logs
+                    if (errorString.length() > 1000) {
+                        errorString = errorString.substring(0, 1000);
+                    }
+                    CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR,
+                            CELL_BROADCAST_MESSAGE_ERROR__TYPE__FAILED_TO_INSERT_TO_DB,
+                            errorString);
+                    Log.e(TAG, "Insert record failed because of unknown reason. " + errorString);
                     return null;
                 }
             default:
-                throw new IllegalArgumentException(
-                        "Insert method doesn't support this uri = " + uri);
+                String errorString = "Insert method doesn't support this uri="
+                        + uri.toString() + " values=" + values;
+                // 1000 character limit for error logs
+                if (errorString.length() > 1000) {
+                    errorString = errorString.substring(0, 1000);
+                }
+                CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR,
+                        CELL_BROADCAST_MESSAGE_ERROR__TYPE__FAILED_TO_INSERT_TO_DB, errorString);
+                throw new IllegalArgumentException(errorString);
         }
     }
 
@@ -299,7 +314,7 @@
     public static String getStringForCellBroadcastTableCreation(String tableName) {
         return "CREATE TABLE " + tableName + " ("
                 + CellBroadcasts._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
-                + CellBroadcasts.SUB_ID + " INTEGER,"
+                + CellBroadcasts.SUBSCRIPTION_ID + " INTEGER,"
                 + CellBroadcasts.SLOT_INDEX + " INTEGER DEFAULT 0,"
                 + CellBroadcasts.GEOGRAPHICAL_SCOPE + " INTEGER,"
                 + CellBroadcasts.PLMN + " TEXT,"
@@ -387,18 +402,26 @@
     private class CellBroadcastPermissionChecker implements PermissionChecker {
         @Override
         public boolean hasWritePermission() {
-            // Only the phone and network statck process has the write permission to modify this
-            // provider.
-            return Binder.getCallingUid() == Process.PHONE_UID
-                    || Binder.getCallingUid() == Process.NETWORK_STACK_UID;
+            // Only the telephony system compontents e.g, Cellbroadcast service has the write
+            // permission to modify this provider.
+            int status = getContext().checkCallingOrSelfPermission(
+                    "android.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS");
+            if (status == PackageManager.PERMISSION_GRANTED) {
+                return true;
+            }
+            return false;
         }
 
         @Override
         public boolean hasReadPermission() {
-            // Only the phone and network stack process has the read permission to query data from
-            // this provider.
-            return Binder.getCallingUid() == Process.PHONE_UID
-                    || Binder.getCallingUid() == Process.NETWORK_STACK_UID;
+            // Only the telephony system compontents e.g, Cellbroadcast service has the read
+            // permission to access this provider.
+            int status = getContext().checkCallingOrSelfPermission(
+                    "android.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS");
+            if (status == PackageManager.PERMISSION_GRANTED) {
+                return true;
+            }
+            return false;
         }
 
         @Override
diff --git a/src/com/android/cellbroadcastservice/DefaultCellBroadcastService.java b/src/com/android/cellbroadcastservice/DefaultCellBroadcastService.java
index bb02f5b..335502f 100644
--- a/src/com/android/cellbroadcastservice/DefaultCellBroadcastService.java
+++ b/src/com/android/cellbroadcastservice/DefaultCellBroadcastService.java
@@ -16,6 +16,8 @@
 
 package com.android.cellbroadcastservice;
 
+import static com.android.cellbroadcastservice.CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_ERROR__TYPE__CDMA_DECODING_ERROR;
+
 import android.content.Context;
 import android.os.Bundle;
 import android.telephony.CellBroadcastService;
@@ -33,8 +35,8 @@
 import java.util.function.Consumer;
 
 /**
- * The default implementation of CellBroadcastService, which is used for handling GSM and CDMA
- * cell broadcast messages.
+ * The default implementation of CellBroadcastService, which is used for handling GSM and CDMA cell
+ * broadcast messages.
  */
 public class DefaultCellBroadcastService extends CellBroadcastService {
     private GsmCellBroadcastHandler mGsmCellBroadcastHandler;
@@ -43,8 +45,8 @@
 
     private static final String TAG = "DefaultCellBroadcastService";
 
-    private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7',
-            '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+    private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7',
+            '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
 
     @Override
     public void onCreate() {
@@ -58,14 +60,25 @@
     }
 
     @Override
+    public void onDestroy() {
+        mGsmCellBroadcastHandler.cleanup();
+        mCdmaCellBroadcastHandler.cleanup();
+        super.onDestroy();
+    }
+
+    @Override
     public void onGsmCellBroadcastSms(int slotIndex, byte[] message) {
         Log.d(TAG, "onGsmCellBroadcastSms received message on slotId=" + slotIndex);
+        CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_REPORTED,
+                CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_REPORTED__TYPE__GSM);
         mGsmCellBroadcastHandler.onGsmCellBroadcastSms(slotIndex, message);
     }
 
     @Override
     public void onCdmaCellBroadcastSms(int slotIndex, byte[] bearerData, int serviceCategory) {
         Log.d(TAG, "onCdmaCellBroadcastSms received message on slotId=" + slotIndex);
+        CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_REPORTED,
+                CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_REPORTED__TYPE__CDMA);
         int[] subIds =
                 ((SubscriptionManager) getSystemService(
                         Context.TELEPHONY_SUBSCRIPTION_SERVICE)).getSubscriptionIds(slotIndex);
@@ -73,12 +86,12 @@
         if (subIds != null && subIds.length > 0) {
             int subId = subIds[0];
             plmn = ((TelephonyManager) getSystemService(
-                            Context.TELEPHONY_SERVICE)).createForSubscriptionId(
-                            subId).getNetworkOperator();
+                    Context.TELEPHONY_SERVICE)).createForSubscriptionId(
+                    subId).getNetworkOperator();
         } else {
             plmn = "";
         }
-        SmsCbMessage message = parseBroadcastSms(getApplicationContext(), slotIndex, plmn,
+        SmsCbMessage message = parseCdmaBroadcastSms(getApplicationContext(), slotIndex, plmn,
                 bearerData, serviceCategory);
         if (message != null) {
             mCdmaCellBroadcastHandler.onCdmaCellBroadcastSms(message);
@@ -89,6 +102,8 @@
     public void onCdmaScpMessage(int slotIndex, List<CdmaSmsCbProgramData> programData,
             String originatingAddress, Consumer<Bundle> callback) {
         Log.d(TAG, "onCdmaScpMessage received message on slotId=" + slotIndex);
+        CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_REPORTED,
+                CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_REPORTED__TYPE__CDMA_SPC);
         mCdmaScpHandler.onCdmaScpMessage(slotIndex, new ArrayList<>(programData),
                 originatingAddress, callback);
     }
@@ -96,18 +111,22 @@
     /**
      * Parses a CDMA broadcast SMS
      *
-     * @param slotIndex the slotIndex the SMS was received on
-     * @param plmn the PLMN for a broadcast SMS or "" if unknown
-     * @param bearerData the bearerData of the SMS
+     * @param slotIndex       the slotIndex the SMS was received on
+     * @param plmn            the PLMN for a broadcast SMS or "" if unknown
+     * @param bearerData      the bearerData of the SMS
      * @param serviceCategory the service category of the broadcast
      */
     @VisibleForTesting
-    public static SmsCbMessage parseBroadcastSms(Context context, int slotIndex, String plmn,
+    public static SmsCbMessage parseCdmaBroadcastSms(Context context, int slotIndex, String plmn,
             byte[] bearerData,
             int serviceCategory) {
-        BearerData bData = BearerData.decode(context, bearerData, serviceCategory);
-        if (bData == null) {
-            Log.w(TAG, "BearerData.decode() returned null");
+        BearerData bData;
+        try {
+            bData = BearerData.decode(context, bearerData, serviceCategory);
+        } catch (Exception e) {
+            Log.e(TAG, "Error decoding bearer data e=" + e.toString());
+            CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR,
+                    CELL_BROADCAST_MESSAGE_ERROR__TYPE__CDMA_DECODING_ERROR, e.toString());
             return null;
         }
         Log.d(TAG, "MT raw BearerData = " + toHexString(bearerData, 0, bearerData.length));
diff --git a/src/com/android/cellbroadcastservice/GsmCellBroadcastHandler.java b/src/com/android/cellbroadcastservice/GsmCellBroadcastHandler.java
index fb04fa0..6aee320 100644
--- a/src/com/android/cellbroadcastservice/GsmCellBroadcastHandler.java
+++ b/src/com/android/cellbroadcastservice/GsmCellBroadcastHandler.java
@@ -16,6 +16,9 @@
 
 package com.android.cellbroadcastservice;
 
+import static com.android.cellbroadcastservice.CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_ERROR__TYPE__GSM_INVALID_PDU;
+import static com.android.cellbroadcastservice.CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_ERROR__TYPE__UNEXPECTED_GSM_MESSAGE_TYPE_FROM_FWK;
+
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.Context;
@@ -234,8 +237,18 @@
                 }
                 if (VDBG) log("Not handled GSM broadcasts.");
             }
+        } else {
+            loge("handleSmsMessage for GSM got object of type: "
+                    + message.obj.getClass().getName());
+            CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR,
+                    CELL_BROADCAST_MESSAGE_ERROR__TYPE__UNEXPECTED_GSM_MESSAGE_TYPE_FROM_FWK,
+                    message.obj.getClass().getName());
         }
-        return super.handleSmsMessage(message);
+        if (message.obj instanceof SmsCbMessage) {
+            return super.handleSmsMessage(message);
+        } else {
+            return false;
+        }
     }
 
     // return the GSM cell location from the first GSM cell info
@@ -367,6 +380,8 @@
 
         } catch (RuntimeException e) {
             loge("Error in decoding SMS CB pdu", e);
+            CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR,
+                    CELL_BROADCAST_MESSAGE_ERROR__TYPE__GSM_INVALID_PDU, e.toString());
             return null;
         }
     }
diff --git a/src/com/android/cellbroadcastservice/GsmSmsCbMessage.java b/src/com/android/cellbroadcastservice/GsmSmsCbMessage.java
index 077f2c6..c24053f 100644
--- a/src/com/android/cellbroadcastservice/GsmSmsCbMessage.java
+++ b/src/com/android/cellbroadcastservice/GsmSmsCbMessage.java
@@ -22,6 +22,9 @@
 import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE;
 import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_TSUNAMI;
 
+import static com.android.cellbroadcastservice.CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_ERROR__TYPE__GSM_INVALID_GEO_FENCING_DATA;
+import static com.android.cellbroadcastservice.CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_ERROR__TYPE__GSM_UMTS_INVALID_WAC;
+
 import android.annotation.NonNull;
 import android.content.Context;
 import android.content.res.Resources;
@@ -206,6 +209,9 @@
             return new GeoFencingTriggerMessage(type, cbIdentifiers);
         } catch (Exception ex) {
             Log.e(TAG, "create geo-fencing trigger failed, ex = " + ex.toString());
+            CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR,
+                    CELL_BROADCAST_MESSAGE_ERROR__TYPE__GSM_INVALID_GEO_FENCING_DATA,
+                    ex.toString());
             return null;
         }
     }
@@ -226,8 +232,13 @@
         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);
+            IllegalArgumentException ex = new IllegalArgumentException(
+                    "Invalid wac data, expected the length of pdu at least " + offset
+                            + wacDataLength + ", actual is " + pdu.length);
+            CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR,
+                    CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_ERROR__TYPE__GSM_UMTS_INVALID_WAC,
+                    ex.toString());
+            throw ex;
         }
 
         BitStreamReader bitReader = new BitStreamReader(pdu, offset);
@@ -268,7 +279,12 @@
                     geo.add(new Circle(center, radius));
                     break;
                 default:
-                    throw new IllegalArgumentException("Unsupported geoType = " + type);
+                    IllegalArgumentException ex = new IllegalArgumentException(
+                            "Unsupported geoType = " + type);
+                    CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR,
+                            CELL_BROADCAST_MESSAGE_ERROR__TYPE__GSM_UMTS_INVALID_WAC,
+                            ex.toString());
+                    throw ex;
             }
         }
         return new Pair(maximumWaitTimeSec, geo);
diff --git a/src/com/android/cellbroadcastservice/SmsCbHeader.java b/src/com/android/cellbroadcastservice/SmsCbHeader.java
index a7efe89..186d9f1 100644
--- a/src/com/android/cellbroadcastservice/SmsCbHeader.java
+++ b/src/com/android/cellbroadcastservice/SmsCbHeader.java
@@ -20,6 +20,8 @@
 import android.telephony.SmsCbEtwsInfo;
 import android.telephony.SmsMessage;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.Arrays;
 import java.util.Locale;
 
@@ -71,7 +73,7 @@
     /**
      * Length of SMS-CB header
      */
-    static final int PDU_HEADER_LENGTH = 6;
+    public static final int PDU_HEADER_LENGTH = 6;
 
     /**
      * GSM pdu format, as defined in 3gpp TS 23.041, section 9.4.1
@@ -129,6 +131,8 @@
 
     public SmsCbHeader(byte[] pdu) throws IllegalArgumentException {
         if (pdu == null || pdu.length < PDU_HEADER_LENGTH) {
+            CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR,
+                    CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_ERROR__TYPE__GSM_INVALID_HEADER_LENGTH);
             throw new IllegalArgumentException("Illegal PDU");
         }
 
@@ -184,7 +188,12 @@
             int messageType = pdu[0];
 
             if (messageType != MESSAGE_TYPE_CBS_MESSAGE) {
-                throw new IllegalArgumentException("Unsupported message type " + messageType);
+                IllegalArgumentException ex = new IllegalArgumentException(
+                        "Unsupported message type " + messageType);
+                CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR,
+                        CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_ERROR__TYPE__GSM_UNSUPPORTED_HEADER_MESSAGE_TYPE,
+                        ex.toString());
+                throw ex;
             }
 
             mMessageIdentifier = ((pdu[1] & 0xff) << 8) | pdu[2] & 0xff;
@@ -224,39 +233,39 @@
         }
     }
 
-    int getGeographicalScope() {
+    public int getGeographicalScope() {
         return mGeographicalScope;
     }
 
-    int getSerialNumber() {
+    public int getSerialNumber() {
         return mSerialNumber;
     }
 
-    int getServiceCategory() {
+    public int getServiceCategory() {
         return mMessageIdentifier;
     }
 
-    int getDataCodingScheme() {
+    public int getDataCodingScheme() {
         return mDataCodingScheme;
     }
 
-    DataCodingScheme getDataCodingSchemeStructedData() {
+    public DataCodingScheme getDataCodingSchemeStructedData() {
         return mDataCodingSchemeStructedData;
     }
 
-    int getPageIndex() {
+    public int getPageIndex() {
         return mPageIndex;
     }
 
-    int getNumberOfPages() {
+    public int getNumberOfPages() {
         return mNrOfPages;
     }
 
-    SmsCbEtwsInfo getEtwsInfo() {
+    public SmsCbEtwsInfo getEtwsInfo() {
         return mEtwsInfo;
     }
 
-    SmsCbCmasInfo getCmasInfo() {
+    public SmsCbCmasInfo getCmasInfo() {
         return mCmasInfo;
     }
 
@@ -264,7 +273,7 @@
      * Return whether this broadcast is an emergency (PWS) message type.
      * @return true if this message is emergency type; false otherwise
      */
-    boolean isEmergencyMessage() {
+    public boolean isEmergencyMessage() {
         return mMessageIdentifier >= SmsCbConstants.MESSAGE_ID_PWS_FIRST_IDENTIFIER
                 && mMessageIdentifier <= SmsCbConstants.MESSAGE_ID_PWS_LAST_IDENTIFIER;
     }
@@ -273,7 +282,8 @@
      * Return whether this broadcast is an ETWS emergency message type.
      * @return true if this message is ETWS emergency type; false otherwise
      */
-    private boolean isEtwsMessage() {
+    @VisibleForTesting
+    public boolean isEtwsMessage() {
         return (mMessageIdentifier & SmsCbConstants.MESSAGE_ID_ETWS_TYPE_MASK)
                 == SmsCbConstants.MESSAGE_ID_ETWS_TYPE;
     }
@@ -282,7 +292,7 @@
      * Return whether this broadcast is an ETWS primary notification.
      * @return true if this message is an ETWS primary notification; false otherwise
      */
-    boolean isEtwsPrimaryNotification() {
+    public boolean isEtwsPrimaryNotification() {
         return mFormat == FORMAT_ETWS_PRIMARY;
     }
 
@@ -290,7 +300,7 @@
      * Return whether this broadcast is in UMTS format.
      * @return true if this message is in UMTS format; false otherwise
      */
-    boolean isUmtsFormat() {
+    public boolean isUmtsFormat() {
         return mFormat == FORMAT_UMTS;
     }
 
@@ -559,6 +569,9 @@
                     // UDH structure not supported
                 case 0x0e:
                     // Defined by the WAP forum not supported
+                    CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR,
+                            CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_ERROR__TYPE__GSM_UNSUPPORTED_HEADER_DATA_CODING_SCHEME,
+                            "Unsupported GSM dataCodingScheme " + dataCodingScheme);
                     throw new IllegalArgumentException("Unsupported GSM dataCodingScheme "
                             + dataCodingScheme);
 
diff --git a/tests/Android.bp b/tests/Android.bp
index e04c259..58ca86a 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -16,7 +16,7 @@
     ],
     srcs: ["src/**/*.java", ":cellbroadcast-shared-srcs"],
     platform_apis: true,
-    test_suites: ["device-tests"],
+    test_suites: ["device-tests", "mts"],
     certificate: "platform",
     instrumentation_for: "CellBroadcastServiceModule",
 }
diff --git a/tests/src/com/android/cellbroadcastservice/tests/CdmaSmsMessageTest.java b/tests/src/com/android/cellbroadcastservice/tests/CdmaSmsMessageTest.java
index 171d437..277326b 100644
--- a/tests/src/com/android/cellbroadcastservice/tests/CdmaSmsMessageTest.java
+++ b/tests/src/com/android/cellbroadcastservice/tests/CdmaSmsMessageTest.java
@@ -43,7 +43,7 @@
 import java.util.Random;
 
 /**
- * Test cases to verify that our parseBroadcastSms function correctly works with the
+ * Test cases to verify that our parseCdmaBroadcastSms function correctly works with the
  * CdmaSmsMessage class.
  */
 @RunWith(AndroidTestingRunner.class)
@@ -448,9 +448,9 @@
                 BearerData.LANGUAGE_ENGLISH, encoding, TEST_TEXT);
 
         SmsCbMessage cbMessage =
-                DefaultCellBroadcastService.parseBroadcastSms(context,
+                DefaultCellBroadcastService.parseCdmaBroadcastSms(context,
                         0, "", msg.getEnvelopeBearerData(), msg.getEnvelopeServiceCategory());
-        //SmsCbMessage cbMessage = msg.parseBroadcastSms("", 0);
+        //SmsCbMessage cbMessage = msg.parseCdmaBroadcastSms("", 0);
         verifyCbValues(cbMessage);
         assertEquals(123, cbMessage.getServiceCategory());
         assertEquals(456, cbMessage.getSerialNumber());
@@ -483,9 +483,9 @@
                 ENCODING_7BIT_ASCII, body, -1, -1, -1, -1, -1);
 
         SmsCbMessage cbMessage =
-                DefaultCellBroadcastService.parseBroadcastSms(context,
+                DefaultCellBroadcastService.parseCdmaBroadcastSms(context,
                         0, "", msg.getEnvelopeBearerData(), msg.getEnvelopeServiceCategory());
-        //SmsCbMessage cbMessage = msg.parseBroadcastSms("", 0);
+        //SmsCbMessage cbMessage = msg.parseCdmaBroadcastSms("", 0);
         verifyCbValues(cbMessage);
         assertEquals(serviceCategory, cbMessage.getServiceCategory());
         assertEquals(1234, cbMessage.getSerialNumber());
@@ -544,9 +544,9 @@
                 SmsCbCmasInfo.CMAS_URGENCY_EXPECTED, SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY);
 
         SmsCbMessage cbMessage =
-                DefaultCellBroadcastService.parseBroadcastSms(mMockedContext,
+                DefaultCellBroadcastService.parseCdmaBroadcastSms(mMockedContext,
                         0, "", msg.getEnvelopeBearerData(), msg.getEnvelopeServiceCategory());
-        //SmsCbMessage cbMessage = msg.parseBroadcastSms("", 0);
+        //SmsCbMessage cbMessage = msg.parseCdmaBroadcastSms("", 0);
         verifyCbValues(cbMessage);
         assertEquals(CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT,
                 cbMessage.getServiceCategory());
@@ -575,9 +575,9 @@
                 0x1F, EXTREME_ALERT, -1, -1, -1, -1, -1);
 
         SmsCbMessage cbMessage =
-                DefaultCellBroadcastService.parseBroadcastSms(mMockedContext,
+                DefaultCellBroadcastService.parseCdmaBroadcastSms(mMockedContext,
                         0, "", msg.getEnvelopeBearerData(), msg.getEnvelopeServiceCategory());
-        //SmsCbMessage cbMessage = msg.parseBroadcastSms("", 0);
+        //SmsCbMessage cbMessage = msg.parseCdmaBroadcastSms("", 0);
         assertNull("expected null for unsupported charset", cbMessage);
     }
 
@@ -590,9 +590,9 @@
                 ENCODING_KOREAN, EXTREME_ALERT, -1, -1, -1, -1, -1);
 
         SmsCbMessage cbMessage =
-                DefaultCellBroadcastService.parseBroadcastSms(mMockedContext,
+                DefaultCellBroadcastService.parseCdmaBroadcastSms(mMockedContext,
                         0, "", msg.getEnvelopeBearerData(), msg.getEnvelopeServiceCategory());
-        //SmsCbMessage cbMessage = msg.parseBroadcastSms("", 0);
+        //SmsCbMessage cbMessage = msg.parseCdmaBroadcastSms("", 0);
         assertNull("expected null for unsupported charset", cbMessage);
     }
 
@@ -606,9 +606,9 @@
                 ENCODING_7BIT_ASCII, null, -1, -1, -1, -1, -1);
 
         SmsCbMessage cbMessage =
-                DefaultCellBroadcastService.parseBroadcastSms(mMockedContext,
+                DefaultCellBroadcastService.parseCdmaBroadcastSms(mMockedContext,
                         0, "", msg.getEnvelopeBearerData(), msg.getEnvelopeServiceCategory());
-        //SmsCbMessage cbMessage = msg.parseBroadcastSms("", 0);
+        //SmsCbMessage cbMessage = msg.parseCdmaBroadcastSms("", 0);
         verifyCbValues(cbMessage);
         assertEquals(CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT,
                 cbMessage.getServiceCategory());
@@ -643,11 +643,11 @@
                 CdmaSmsMessage cdmaSmsMessage = createBroadcastParcel(category);
                 SmsMessage msg = createMessageFromParcel(cdmaSmsMessage, data);
                 SmsCbMessage cbMessage =
-                        DefaultCellBroadcastService.parseBroadcastSms(
+                        DefaultCellBroadcastService.parseCdmaBroadcastSms(
                                 mMockedContext,
                                 0, "", msg.getEnvelopeBearerData(),
                                 msg.getEnvelopeServiceCategory());
-                //SmsCbMessage cbMessage = msg.parseBroadcastSms("", 0);
+                //SmsCbMessage cbMessage = msg.parseCdmaBroadcastSms("", 0);
                 // with random input, cbMessage will almost always be null (log when it isn't)
                 if (cbMessage != null) {
                     Log.d(TAG, "success: " + cbMessage);
@@ -682,9 +682,9 @@
 
                 SmsMessage msg = createMessageFromParcel(cdmaSmsMessage, bos.toByteArray());
                 SmsCbMessage cbMessage =
-                        DefaultCellBroadcastService.parseBroadcastSms(mMockedContext, 0, "",
+                        DefaultCellBroadcastService.parseCdmaBroadcastSms(mMockedContext, 0, "",
                                 msg.getEnvelopeBearerData(), msg.getEnvelopeServiceCategory());
-                //SmsCbMessage cbMessage = msg.parseBroadcastSms("", 0);
+                //SmsCbMessage cbMessage = msg.parseCdmaBroadcastSms("", 0);
             } catch (Exception e) {
                 Log.d(TAG, "exception thrown", e);
                 fail("Exception in decoder at run " + run + " length " + len + ": " + e);
@@ -854,9 +854,9 @@
         SmsMessage msg = createMessageFromParcel(cdmaSmsMessage, CMAS_TEST_BEARER_DATA);
 
         SmsCbMessage cbMessage =
-                DefaultCellBroadcastService.parseBroadcastSms(mMockedContext,
+                DefaultCellBroadcastService.parseCdmaBroadcastSms(mMockedContext,
                         0, "", msg.getEnvelopeBearerData(), msg.getEnvelopeServiceCategory());
-        //SmsCbMessage cbMessage = msg.parseBroadcastSms("", 0);
+        //SmsCbMessage cbMessage = msg.parseCdmaBroadcastSms("", 0);
         assertNotNull("expected non-null for bearer data", cbMessage);
         assertEquals("geoScope", cbMessage.getGeographicalScope(), 1);
         assertEquals("serialNumber", cbMessage.getSerialNumber(), 51072);
diff --git a/tests/src/com/android/cellbroadcastservice/tests/CellBroadcastHandlerTest.java b/tests/src/com/android/cellbroadcastservice/tests/CellBroadcastHandlerTest.java
index 3fce3c5..b7fbdf5 100644
--- a/tests/src/com/android/cellbroadcastservice/tests/CellBroadcastHandlerTest.java
+++ b/tests/src/com/android/cellbroadcastservice/tests/CellBroadcastHandlerTest.java
@@ -69,7 +69,7 @@
                 mc.addRow(new Object[]{
                         1,              // _ID
                         0,              // SLOT_INDEX
-                        1,              // SUB_ID
+                        1,              // SUBSCRIPTION_ID
                         0,              // GEOGRAPHICAL_SCOPE
                         "311480",       // PLMN
                         0,              // LAC
diff --git a/tests/src/com/android/cellbroadcastservice/tests/CellBroadcastServiceTestBase.java b/tests/src/com/android/cellbroadcastservice/tests/CellBroadcastServiceTestBase.java
index 009986e..c230f2d 100644
--- a/tests/src/com/android/cellbroadcastservice/tests/CellBroadcastServiceTestBase.java
+++ b/tests/src/com/android/cellbroadcastservice/tests/CellBroadcastServiceTestBase.java
@@ -18,6 +18,7 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
@@ -135,6 +136,7 @@
             return null;
         }).when(mMockedContext).registerReceiver(
                 any(BroadcastReceiver.class), any(IntentFilter.class));
+        doReturn(true).when(mMockedLocationManager).isProviderEnabled(anyString());
     }
 
     protected void tearDown() throws Exception {
diff --git a/tests/src/com/android/cellbroadcastservice/tests/GsmCellBroadcastHandlerTest.java b/tests/src/com/android/cellbroadcastservice/tests/GsmCellBroadcastHandlerTest.java
index 65f0c4b..9132989 100644
--- a/tests/src/com/android/cellbroadcastservice/tests/GsmCellBroadcastHandlerTest.java
+++ b/tests/src/com/android/cellbroadcastservice/tests/GsmCellBroadcastHandlerTest.java
@@ -21,6 +21,7 @@
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.content.ContentValues;
@@ -28,6 +29,8 @@
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.database.MatrixCursor;
+import android.location.Location;
+import android.location.LocationRequest;
 import android.net.Uri;
 import android.os.Bundle;
 import android.provider.Settings;
@@ -52,8 +55,10 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 
 import java.util.Map;
+import java.util.function.Consumer;
 
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -81,7 +86,7 @@
                 mc.addRow(new Object[]{
                         1,              // _ID
                         0,              // SLOT_INDEX
-                        1,              // SUB_ID
+                        1,              // SUBSCRIPTION_ID
                         0,              // GEOGRAPHICAL_SCOPE
                         "311480",       // PLMN
                         0,              // LAC
@@ -168,6 +173,7 @@
     @Test
     @SmallTest
     public void testTriggerMessage() throws Exception {
+        doReturn(false).when(mMockedLocationManager).isProviderEnabled(anyString());
         final byte[] pdu = hexStringToBytes("0001113001010010C0111204D2");
         mGsmCellBroadcastHandler.onGsmCellBroadcastSms(0, pdu);
         mTestableLooper.processAllMessages();
@@ -202,4 +208,27 @@
         verify(mMockedContext, never()).sendOrderedBroadcast(any(), anyString(), anyString(),
                 any(), any(), anyInt(), any(), any());
     }
+
+    @Test
+    @SmallTest
+    public void testGeofencingAlertOutOfPolygon() {
+        final byte[] pdu = hexStringToBytes("01111D7090010254747A0E4ACF416110B538A582DE6650906AA28"
+                + "2AE6979995D9ECF41C576597E2EBBC77950905D96D3D3EE33689A9FD3CB6D1708CA2E87E76550FAE"
+                + "C7ECBCB203ABA0C6A97E7F3F0B9EC02C15CB5769A5D0652A030FB1ECECF5D5076393C2F83C8E9B9B"
+                + "C7C0ECBC9203A3A3D07B5CBF379F85C06E16030580D660BB662B51A0D57CC3500000000000000000"
+                + "0000000000000000000000000000000000000000000000000003021002078B53B6CA4B84B53988A4"
+                + "B86B53958A4C2DB53B54A4C28B53B6CA4B840100CFF");
+        mGsmCellBroadcastHandler.onGsmCellBroadcastSms(0, pdu);
+        mTestableLooper.processAllMessages();
+
+        ArgumentCaptor<Consumer<Location>> captor = ArgumentCaptor.forClass(Consumer.class);
+        verify(mMockedLocationManager, times(2)).getCurrentLocation(
+                any(LocationRequest.class), any(), any(), captor.capture());
+
+        Consumer<Location> consumer = captor.getValue();
+        consumer.accept(Mockito.mock(Location.class));
+
+        verify(mMockedContext, never()).sendOrderedBroadcast(any(), anyString(), anyString(),
+                any(), any(), anyInt(), any(), any());
+    }
 }