Support SMS/WAP initiated SUPL

Support SMS/WAP NI SUPL message injection, this feature can be
enabled by setting ENABLE_NI_SUPL_MESSAGE_INJECTION=true
in gps.conf.

Test: 1. Set ENABLE_NI_SUPL_MESSAGE_INJECTION to true in gps_debug.conf
      2. Enable NFW lock
      3. atest SmsNiSuplTest
      4. Confirm the SUPL message is injected to HAL
Bug: 242105192

Change-Id: I97c59cd2d1e6e7d698cca458cf18bc2666608bf8
diff --git a/services/core/java/com/android/server/location/gnss/GnssConfiguration.java b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
index 1435016..b8abd98 100644
--- a/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
+++ b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
@@ -76,6 +76,8 @@
             "ENABLE_PSDS_PERIODIC_DOWNLOAD";
     private static final String CONFIG_ENABLE_ACTIVE_SIM_EMERGENCY_SUPL =
             "ENABLE_ACTIVE_SIM_EMERGENCY_SUPL";
+    private static final String CONFIG_ENABLE_NI_SUPL_MESSAGE_INJECTION =
+            "ENABLE_NI_SUPL_MESSAGE_INJECTION";
     static final String CONFIG_LONGTERM_PSDS_SERVER_1 = "LONGTERM_PSDS_SERVER_1";
     static final String CONFIG_LONGTERM_PSDS_SERVER_2 = "LONGTERM_PSDS_SERVER_2";
     static final String CONFIG_LONGTERM_PSDS_SERVER_3 = "LONGTERM_PSDS_SERVER_3";
@@ -218,6 +220,14 @@
     }
 
     /**
+     * Returns true if NI SUPL message injection is enabled; Returns false otherwise.
+     * Default false if not set.
+     */
+    boolean isNiSuplMessageInjectionEnabled() {
+        return getBooleanConfig(CONFIG_ENABLE_NI_SUPL_MESSAGE_INJECTION, false);
+    }
+
+    /**
      * Returns true if a long-term PSDS server is configured.
      */
     boolean isLongTermPsdsServerConfigured() {
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index 6f637b8..6f6b1c9 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -84,6 +84,7 @@
 import android.os.WorkSource;
 import android.os.WorkSource.WorkChain;
 import android.provider.Settings;
+import android.provider.Telephony.Sms.Intents;
 import android.telephony.CarrierConfigManager;
 import android.telephony.CellIdentity;
 import android.telephony.CellIdentityGsm;
@@ -95,6 +96,7 @@
 import android.telephony.CellInfoLte;
 import android.telephony.CellInfoNr;
 import android.telephony.CellInfoWcdma;
+import android.telephony.SmsMessage;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
@@ -107,6 +109,7 @@
 import com.android.internal.location.GpsNetInitiatedHandler;
 import com.android.internal.location.GpsNetInitiatedHandler.GpsNiNotification;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.HexDump;
 import com.android.server.FgThread;
 import com.android.server.location.gnss.GnssSatelliteBlocklistHelper.GnssSatelliteBlocklistCallback;
 import com.android.server.location.gnss.NtpTimeHelper.InjectNtpTimeCallback;
@@ -523,23 +526,31 @@
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
         intentFilter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
-        mContext.registerReceiver(new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                String action = intent.getAction();
-                if (DEBUG) Log.d(TAG, "receive broadcast intent, action: " + action);
-                if (action == null) {
-                    return;
-                }
+        mContext.registerReceiver(mIntentReceiver, intentFilter, null, mHandler);
 
-                switch (action) {
-                    case CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED:
-                    case TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED:
-                        subscriptionOrCarrierConfigChanged();
-                        break;
-                }
+        if (mNetworkConnectivityHandler.isNativeAgpsRilSupported()
+                && mGnssConfiguration.isNiSuplMessageInjectionEnabled()) {
+            // Listen to WAP PUSH NI SUPL message.
+            // See User Plane Location Protocol Candidate Version 3.0,
+            // OMA-TS-ULP-V3_0-20110920-C, Section 8.3 OMA Push.
+            intentFilter = new IntentFilter();
+            intentFilter.addAction(Intents.WAP_PUSH_RECEIVED_ACTION);
+            try {
+                intentFilter.addDataType("application/vnd.omaloc-supl-init");
+            } catch (IntentFilter.MalformedMimeTypeException e) {
+                Log.w(TAG, "Malformed SUPL init mime type");
             }
-        }, intentFilter, null, mHandler);
+            mContext.registerReceiver(mIntentReceiver, intentFilter, null, mHandler);
+
+            // Listen to MT SMS NI SUPL message.
+            // See User Plane Location Protocol Candidate Version 3.0,
+            // OMA-TS-ULP-V3_0-20110920-C, Section 8.4 MT SMS.
+            intentFilter = new IntentFilter();
+            intentFilter.addAction(Intents.DATA_SMS_RECEIVED_ACTION);
+            intentFilter.addDataScheme("sms");
+            intentFilter.addDataAuthority("localhost", "7275");
+            mContext.registerReceiver(mIntentReceiver, intentFilter, null, mHandler);
+        }
 
         mNetworkConnectivityHandler.registerNetworkCallbacks();
 
@@ -560,6 +571,80 @@
         updateEnabled();
     }
 
+    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (DEBUG) Log.d(TAG, "receive broadcast intent, action: " + action);
+            if (action == null) {
+                return;
+            }
+
+            switch (action) {
+                case CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED:
+                case TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED:
+                    subscriptionOrCarrierConfigChanged();
+                    break;
+                case Intents.WAP_PUSH_RECEIVED_ACTION:
+                case Intents.DATA_SMS_RECEIVED_ACTION:
+                    injectSuplInit(intent);
+                    break;
+            }
+        }
+    };
+
+    private void injectSuplInit(Intent intent) {
+        if (!isNfwLocationAccessAllowed()) {
+            Log.w(TAG, "Reject SUPL INIT as no NFW location access");
+            return;
+        }
+
+        int slotIndex = intent.getIntExtra(SubscriptionManager.EXTRA_SLOT_INDEX,
+                SubscriptionManager.INVALID_SIM_SLOT_INDEX);
+        if (slotIndex == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
+            Log.e(TAG, "Invalid slot index");
+            return;
+        }
+
+        byte[] suplInit = null;
+        String action = intent.getAction();
+        if (action.equals(Intents.DATA_SMS_RECEIVED_ACTION)) {
+            SmsMessage[] messages = Intents.getMessagesFromIntent(intent);
+            if (messages == null) {
+                Log.e(TAG, "Message does not exist in the intent");
+                return;
+            }
+            for (SmsMessage message : messages) {
+                suplInit = message.getUserData();
+                injectSuplInit(suplInit, slotIndex);
+            }
+        } else if (action.equals(Intents.WAP_PUSH_RECEIVED_ACTION)) {
+            suplInit = intent.getByteArrayExtra("data");
+            injectSuplInit(suplInit, slotIndex);
+        }
+    }
+
+    private void injectSuplInit(byte[] suplInit, int slotIndex) {
+        if (suplInit != null) {
+            if (DEBUG) {
+                Log.d(TAG, "suplInit = "
+                        + HexDump.toHexString(suplInit) + " slotIndex = " + slotIndex);
+            }
+            mGnssNative.injectNiSuplMessageData(suplInit, suplInit.length , slotIndex);
+        }
+    }
+
+    private boolean isNfwLocationAccessAllowed() {
+        if (mGnssNative.isInEmergencySession()) {
+            return true;
+        }
+        if (mGnssVisibilityControl != null
+                && mGnssVisibilityControl.hasLocationPermissionEnabledProxyApps()) {
+            return true;
+        }
+        return false;
+    }
+
     /**
      * Implements {@link InjectNtpTimeCallback#injectTime}
      */
diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
index 02bdfd5..a7fffe2 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
@@ -762,6 +762,10 @@
         return APN_INVALID;
     }
 
+    protected boolean isNativeAgpsRilSupported() {
+        return native_is_agps_ril_supported();
+    }
+
     // AGPS support
     private native void native_agps_data_conn_open(long networkHandle, String apn, int apnIpType);
 
diff --git a/services/core/java/com/android/server/location/gnss/GnssVisibilityControl.java b/services/core/java/com/android/server/location/gnss/GnssVisibilityControl.java
index 631dbbf..4e5e5f8 100644
--- a/services/core/java/com/android/server/location/gnss/GnssVisibilityControl.java
+++ b/services/core/java/com/android/server/location/gnss/GnssVisibilityControl.java
@@ -437,6 +437,10 @@
         return locationPermissionEnabledProxyApps;
     }
 
+    public boolean hasLocationPermissionEnabledProxyApps() {
+        return getLocationPermissionEnabledProxyApps().length > 0;
+    }
+
     private void handleNfwNotification(NfwNotification nfwNotification) {
         if (DEBUG) Log.d(TAG, "Non-framework location access notification: " + nfwNotification);
 
diff --git a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
index 2d015a5d..edb2e5b 100644
--- a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
+++ b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
@@ -989,6 +989,14 @@
         mGnssHal.injectPsdsData(data, length, psdsType);
     }
 
+    /**
+     * Injects NI SUPL message data into the GNSS HAL.
+     */
+    public void injectNiSuplMessageData(byte[] data, int length, int slotIndex) {
+        Preconditions.checkState(mRegistered);
+        mGnssHal.injectNiSuplMessageData(data, length, slotIndex);
+    }
+
     @NativeEntryPoint
     void reportGnssServiceDied() {
         // Not necessary to clear (and restore) binder identity since it runs on another thread.
@@ -1278,7 +1286,7 @@
     }
 
     @NativeEntryPoint
-    boolean isInEmergencySession() {
+    public boolean isInEmergencySession() {
         return Binder.withCleanCallingIdentity(
                 () -> mEmergencyHelper.isInEmergency(
                         TimeUnit.SECONDS.toMillis(mConfiguration.getEsExtensionSec())));
@@ -1507,6 +1515,10 @@
         protected void injectPsdsData(byte[] data, int length, int psdsType) {
             native_inject_psds_data(data, length, psdsType);
         }
+
+        protected void injectNiSuplMessageData(byte[] data, int length, int slotIndex) {
+            native_inject_ni_supl_message_data(data, length, slotIndex);
+        }
     }
 
     // basic APIs
@@ -1650,6 +1662,9 @@
     private static native void native_agps_set_ref_location_cellid(int type, int mcc, int mnc,
             int lac, long cid, int tac, int pcid, int arfcn);
 
+    private static native void native_inject_ni_supl_message_data(byte[] data, int length,
+            int slotIndex);
+
     // PSDS APIs
 
     private static native boolean native_supports_psds();
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 9fa23c2..e1de05c 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -482,6 +482,17 @@
     agnssRilIface->setSetId(type, setid_string);
 }
 
+static void android_location_gnss_hal_GnssNative_inject_ni_supl_message_data(JNIEnv* env, jclass,
+                                                                             jbyteArray data,
+                                                                             jint length,
+                                                                             jint slotIndex) {
+    if (agnssRilIface == nullptr) {
+        ALOGE("%s: IAGnssRil interface not available.", __func__);
+        return;
+    }
+    agnssRilIface->injectNiSuplMessageData(data, length, slotIndex);
+}
+
 static jint android_location_gnss_hal_GnssNative_read_nmea(JNIEnv* env, jclass,
                                                            jbyteArray nmeaArray, jint buffer_size) {
     return gnssHal->readNmea(nmeaArray, buffer_size);
@@ -974,6 +985,8 @@
                  android_location_gnss_hal_GnssNative_agps_set_reference_location_cellid)},
         {"native_set_agps_server", "(ILjava/lang/String;I)V",
          reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_set_agps_server)},
+        {"native_inject_ni_supl_message_data", "([BII)V",
+         reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_inject_ni_supl_message_data)},
         {"native_send_ni_response", "(II)V",
          reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_send_ni_response)},
         {"native_get_internal_state", "()Ljava/lang/String;",
diff --git a/services/core/jni/gnss/AGnssRil.cpp b/services/core/jni/gnss/AGnssRil.cpp
index 34e4976..c7a1af7 100644
--- a/services/core/jni/gnss/AGnssRil.cpp
+++ b/services/core/jni/gnss/AGnssRil.cpp
@@ -84,8 +84,19 @@
     networkAttributes.capabilities = static_cast<int32_t>(capabilities),
     networkAttributes.apn = jniApn.c_str();
 
-    auto result = mIAGnssRil->updateNetworkState(networkAttributes);
-    return checkAidlStatus(result, "IAGnssRilAidl updateNetworkState() failed.");
+    auto status = mIAGnssRil->updateNetworkState(networkAttributes);
+    return checkAidlStatus(status, "IAGnssRilAidl updateNetworkState() failed.");
+}
+
+jboolean AGnssRil::injectNiSuplMessageData(const jbyteArray& msgData, jint length, jint slotIndex) {
+    JNIEnv* env = getJniEnv();
+    jbyte* bytes = reinterpret_cast<jbyte*>(env->GetPrimitiveArrayCritical(msgData, 0));
+    auto status = mIAGnssRil->injectNiSuplMessageData(std::vector<uint8_t>((const uint8_t*)bytes,
+                                                                           (const uint8_t*)bytes +
+                                                                                   length),
+                                                      static_cast<int>(slotIndex));
+    env->ReleasePrimitiveArrayCritical(msgData, bytes, JNI_ABORT);
+    return checkAidlStatus(status, "IAGnssRil injectNiSuplMessageData() failed.");
 }
 
 // Implementation of AGnssRil_V1_0
@@ -151,6 +162,11 @@
     return checkHidlReturn(result, "IAGnssRil_V1_0 updateNetworkState() failed.");
 }
 
+jboolean AGnssRil_V1_0::injectNiSuplMessageData(const jbyteArray&, jint, jint) {
+    ALOGI("IAGnssRil_V1_0 interface does not support injectNiSuplMessageData.");
+    return JNI_FALSE;
+}
+
 // Implementation of AGnssRil_V2_0
 
 AGnssRil_V2_0::AGnssRil_V2_0(const sp<IAGnssRil_V2_0>& iAGnssRil)
diff --git a/services/core/jni/gnss/AGnssRil.h b/services/core/jni/gnss/AGnssRil.h
index ce14a77d..b7e0282 100644
--- a/services/core/jni/gnss/AGnssRil.h
+++ b/services/core/jni/gnss/AGnssRil.h
@@ -43,6 +43,8 @@
     virtual jboolean updateNetworkState(jboolean connected, jint type, jboolean roaming,
                                         jboolean available, const jstring& apn, jlong networkHandle,
                                         jshort capabilities) = 0;
+    virtual jboolean injectNiSuplMessageData(const jbyteArray& msgData, jint length,
+                                             jint slotIndex) = 0;
 };
 
 class AGnssRil : public AGnssRilInterface {
@@ -55,6 +57,8 @@
     jboolean updateNetworkState(jboolean connected, jint type, jboolean roaming, jboolean available,
                                 const jstring& apn, jlong networkHandle,
                                 jshort capabilities) override;
+    jboolean injectNiSuplMessageData(const jbyteArray& msgData, jint length,
+                                     jint slotIndex) override;
 
 private:
     const sp<android::hardware::gnss::IAGnssRil> mIAGnssRil;
@@ -70,6 +74,7 @@
     jboolean updateNetworkState(jboolean connected, jint type, jboolean roaming, jboolean available,
                                 const jstring& apn, jlong networkHandle,
                                 jshort capabilities) override;
+    jboolean injectNiSuplMessageData(const jbyteArray&, jint, jint) override;
 
 private:
     const sp<android::hardware::gnss::V1_0::IAGnssRil> mAGnssRil_V1_0;