[automerger skipped] Merge "Use SystemApi to broadcast to background receivers" am: a2833181ce am: 460fe4fba9 am: 34d97bfbb9 -s ours
am skip reason: Change-Id I4bcac6e635bd2303bc4f9f82425984fefc85a793 with SHA-1 c6c9153719 is in history

Change-Id: Ieec48e73872f7eae0b92e7bf7364a81a52ed84df
diff --git a/Android.bp b/Android.bp
index 20746b7..c9a7f28 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,7 +1,7 @@
 // Copyright 2019 The Android Open Source Project
-
-android_app {
-    name: "CellBroadcastServiceModule",
+java_defaults {
+    name: "CellBroadcastServiceCommon",
+    min_sdk_version: "29",
     srcs: [
       "src/**/*.java",
       ":framework-annotations",
@@ -18,6 +18,25 @@
     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",
 }
 
 // used to share common constants between cellbroadcastservice and cellbroadcastreceier
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/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/CellBroadcastHandler.java b/src/com/android/cellbroadcastservice/CellBroadcastHandler.java
index 11b0965..3dfc4d6 100644
--- a/src/com/android/cellbroadcastservice/CellBroadcastHandler.java
+++ b/src/com/android/cellbroadcastservice/CellBroadcastHandler.java
@@ -16,6 +16,9 @@
 
 package com.android.cellbroadcastservice;
 
+import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -30,11 +33,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;
@@ -51,7 +55,6 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.cdma.CdmaSmsCbProgramData;
 import android.text.TextUtils;
-import android.text.format.DateUtils;
 import android.util.LocalLog;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -65,6 +68,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 +101,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;
@@ -124,7 +128,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[][] {
@@ -347,7 +351,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 +359,6 @@
                         && message.getEtwsWarningInfo().isPrimary()
                         != messageToCheck.getEtwsWarningInfo().isPrimary()) {
                     // Not a dup. Check next one.
-                    log("ETWS primary check. Not a dup. " + messageToCheck);
                     continue;
                 }
 
@@ -370,7 +372,6 @@
                                 messageToCheck.getServiceCategory()),
                         message.getServiceCategory())) {
                     // Not a dup. Check next one.
-                    log("Service category check. Not a dup. " + messageToCheck);
                     continue;
                 }
 
@@ -585,18 +586,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
@@ -606,17 +598,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;
         }
 
         /**
@@ -624,103 +619,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) {
+                Rlog.d(TAG, "Got location update");
+                latLng = new LatLng(location.getLatitude(), location.getLongitude());
+            } else if (mNumLocationUpdatesInProgress > 0) {
+                Rlog.d(TAG, "Still waiting for " + mNumLocationUpdatesInProgress
+                        + " more location updates.");
+                return;
+            } else {
+                Rlog.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) Rlog.d(TAG, "requestLocationUpdate");
-            if (!isLocationServiceAvailable()) {
+            if (!hasPermission(ACCESS_FINE_LOCATION) && !hasPermission(ACCESS_COARSE_LOCATION)) {
                 if (DBG) {
                     Rlog.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) {
+                            Rlog.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) Rlog.d(TAG, "location request timeout");
-                        onLocationUpdate(null);
-                        break;
-                    case EVENT_REQUEST_LOCATION_UPDATE:
-                        requestLocationUpdateInternal((LocationUpdateCallback) msg.obj, msg.arg1);
-                        break;
-                    default:
-                        Rlog.e(TAG, "Unsupported message type " + msg.what);
-                }
-            }
-        }
     }
 }
diff --git a/src/com/android/cellbroadcastservice/CellBroadcastProvider.java b/src/com/android/cellbroadcastservice/CellBroadcastProvider.java
index 34fbf66..bcf0bcc 100644
--- a/src/com/android/cellbroadcastservice/CellBroadcastProvider.java
+++ b/src/com/android/cellbroadcastservice/CellBroadcastProvider.java
@@ -388,18 +388,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/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/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..1fefbbf 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
@@ -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());
+    }
 }