diff options
5 files changed, 549 insertions, 8 deletions
diff --git a/services/core/java/com/android/server/location/GnssConfiguration.java b/services/core/java/com/android/server/location/GnssConfiguration.java index 29465ad366f4..9e339430da12 100644 --- a/services/core/java/com/android/server/location/GnssConfiguration.java +++ b/services/core/java/com/android/server/location/GnssConfiguration.java @@ -29,7 +29,10 @@ import libcore.io.IoUtils; import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; @@ -66,6 +69,7 @@ class GnssConfiguration { "USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL"; private static final String CONFIG_GPS_LOCK = "GPS_LOCK"; private static final String CONFIG_ES_EXTENSION_SEC = "ES_EXTENSION_SEC"; + public static final String CONFIG_NFW_PROXY_APPS = "NFW_PROXY_APPS"; // Limit on NI emergency mode time extension after emergency sessions ends private static final int MAX_EMERGENCY_MODE_EXTENSION_SECONDS = 300; // 5 minute maximum @@ -171,6 +175,32 @@ class GnssConfiguration { } /** + * Returns the list of proxy apps from the value of config parameter NFW_PROXY_APPS or + * {@Collections.EMPTY_LIST} if no value is provided. + */ + List<String> getProxyApps() { + // Space separated list of Android proxy app package names. + String proxyAppsStr = mProperties.getProperty(CONFIG_NFW_PROXY_APPS); + if (TextUtils.isEmpty(proxyAppsStr)) { + return Collections.EMPTY_LIST; + } + + String[] proxyAppsArray = proxyAppsStr.trim().split("\\s+"); + if (proxyAppsArray.length == 0) { + return Collections.EMPTY_LIST; + } + + // TODO(b/122856486): Validate proxy app names so that a system app or some popular app + // with location permission is not specified as a proxy app. + ArrayList proxyApps = new ArrayList(proxyAppsArray.length); + for (String proxyApp : proxyAppsArray) { + proxyApps.add(proxyApp); + } + + return proxyApps; + } + + /** * Updates the GNSS HAL satellite blacklist. */ void setSatelliteBlacklist(int[] constellations, int[] svids) { diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java index 269767ac3be2..91ba705b230a 100644 --- a/services/core/java/com/android/server/location/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/GnssLocationProvider.java @@ -371,6 +371,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements private boolean mSuplEsEnabled = false; private final Context mContext; + private final Looper mLooper; private final LocationExtras mLocationExtras = new LocationExtras(); private final GnssStatusListenerHelper mGnssStatusListenerHelper; private final GnssSatelliteBlacklistHelper mGnssSatelliteBlacklistHelper; @@ -381,6 +382,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements private final NtpTimeHelper mNtpTimeHelper; private final GnssBatchingProvider mGnssBatchingProvider; private final GnssGeofenceProvider mGnssGeofenceProvider; + private GnssVisibilityControl mGnssVisibilityControl; // Handler for processing events private Handler mHandler; @@ -555,6 +557,9 @@ public class GnssLocationProvider extends AbstractLocationProvider implements mC2KServerPort = mGnssConfiguration.getC2KPort(TCP_MIN_PORT); mNIHandler.setEmergencyExtensionSeconds(mGnssConfiguration.getEsExtensionSec()); mSuplEsEnabled = mGnssConfiguration.getSuplEs(0) == 1; + if (mGnssVisibilityControl != null) { + mGnssVisibilityControl.updateProxyApps(mGnssConfiguration.getProxyApps()); + } } public GnssLocationProvider(Context context, LocationProviderManager locationProviderManager, @@ -562,6 +567,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements super(locationProviderManager); mContext = context; + mLooper = looper; // Create a wake lock mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); @@ -1834,6 +1840,27 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } } + // Implements method nfwNotifyCb() in IGnssVisibilityControlCallback.hal. + @NativeEntryPoint + private void reportNfwNotification(String proxyAppPackageName, byte protocolStack, + String otherProtocolStackName, byte requestor, String requestorId, byte responseType, + boolean inEmergencyMode, boolean isCachedLocation) { + if (mGnssVisibilityControl == null) { + Log.e(TAG, "reportNfwNotification: mGnssVisibilityControl is not initialized."); + return; + } + + mGnssVisibilityControl.reportNfwNotification(proxyAppPackageName, protocolStack, + otherProtocolStackName, requestor, requestorId, responseType, inEmergencyMode, + isCachedLocation); + } + + // Implements method isInEmergencySession() in IGnssVisibilityControlCallback.hal. + @NativeEntryPoint + boolean isInEmergencySession() { + return mNIHandler.getInEmergency(); + } + private void sendMessage(int message, int arg, Object obj) { // hold a wake lock until this message is delivered // note that this assumes the message will not be removed from the queue before @@ -1922,6 +1949,10 @@ public class GnssLocationProvider extends AbstractLocationProvider implements native_cleanup(); } + if (native_is_gnss_visibility_control_supported()) { + mGnssVisibilityControl = new GnssVisibilityControl(mContext, mLooper); + } + // load default GPS configuration // (this configuration might change in the future based on SIM changes) reloadGpsProperties(); @@ -2079,6 +2110,8 @@ public class GnssLocationProvider extends AbstractLocationProvider implements private static native boolean native_is_supported(); + private static native boolean native_is_gnss_visibility_control_supported(); + private static native void native_init_once(); private native boolean native_init(); diff --git a/services/core/java/com/android/server/location/GnssVisibilityControl.java b/services/core/java/com/android/server/location/GnssVisibilityControl.java new file mode 100644 index 000000000000..845aa9dfacd6 --- /dev/null +++ b/services/core/java/com/android/server/location/GnssVisibilityControl.java @@ -0,0 +1,362 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.location; + +import android.annotation.SuppressLint; +import android.app.AppOpsManager; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Handler; +import android.os.Looper; +import android.os.PowerManager; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.Log; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Handles GNSS non-framework location access user visibility and control. + * + * The state of the GnssVisibilityControl object must be accessed/modified through the Handler + * thread only. + */ +class GnssVisibilityControl { + private static final String TAG = "GnssVisibilityControl"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + // Constants related to non-framework (NFW) location access permission proxy apps. + private static final String NFW_PROXY_APP_PKG_ACTIVITY_NAME_SUFFIX = + ".NonFrameworkLocationAccessActivity"; + private static final String NFW_INTENT_ACTION_NFW_LOCATION_ACCESS_SUFFIX = + ".intent.action.NON_FRAMEWORK_LOCATION_ACCESS"; + private static final String NFW_INTENT_TYPE = "text/plain"; + + private static final String LOCATION_PERMISSION_NAME = + "android.permission.ACCESS_FINE_LOCATION"; + + // Wakelocks + private static final String WAKELOCK_KEY = TAG; + private static final long WAKELOCK_TIMEOUT_MILLIS = 60 * 1000; + private final PowerManager.WakeLock mWakeLock; + + private final AppOpsManager mAppOps; + private final PackageManager mPackageManager; + + private final Handler mHandler; + private final Context mContext; + + // Number of non-framework location access proxy apps is expected to be small (< 5). + private static final int HASH_MAP_INITIAL_CAPACITY_PROXY_APP_TO_LOCATION_PERMISSIONS = 7; + private HashMap<String, Boolean> mProxyAppToLocationPermissions = new HashMap<>( + HASH_MAP_INITIAL_CAPACITY_PROXY_APP_TO_LOCATION_PERMISSIONS); + + private PackageManager.OnPermissionsChangedListener mOnPermissionsChangedListener = + uid -> postEvent(() -> handlePermissionsChanged(uid)); + + GnssVisibilityControl(Context context, Looper looper) { + mContext = context; + PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY); + mHandler = new Handler(looper); + mAppOps = mContext.getSystemService(AppOpsManager.class); + mPackageManager = mContext.getPackageManager(); + // TODO(b/122855984): Handle global location settings on/off. + // TODO(b/122856189): Handle roaming case. + } + + void updateProxyApps(List<String> nfwLocationAccessProxyApps) { + // NOTE: This class doesn't explicitly register and listen for SIM_STATE_CHANGED event + // but rather piggy backs on the GnssLocationProvider SIM_STATE_CHANGED handling + // so that the order of processing is preserved. GnssLocationProvider should + // first load the new config parameters for the new SIM and then call this method. + postEvent(() -> handleSubscriptionOrSimChanged(nfwLocationAccessProxyApps)); + } + + void reportNfwNotification(String proxyAppPackageName, byte protocolStack, + String otherProtocolStackName, byte requestor, String requestorId, byte responseType, + boolean inEmergencyMode, boolean isCachedLocation) { + postEvent(() -> handleNfwNotification( + new NfwNotification(proxyAppPackageName, protocolStack, otherProtocolStackName, + requestor, requestorId, responseType, inEmergencyMode, isCachedLocation))); + } + + private void handleSubscriptionOrSimChanged(List<String> nfwLocationAccessProxyApps) { + if (nfwLocationAccessProxyApps.isEmpty()) { + // Stop listening for app permission changes. Clear the app list in GNSS HAL. + if (!mProxyAppToLocationPermissions.isEmpty()) { + mPackageManager.removeOnPermissionsChangeListener(mOnPermissionsChangedListener); + mProxyAppToLocationPermissions.clear(); + updateNfwLocationAccessProxyAppsInGnssHal(); + } + return; + } + + if (mProxyAppToLocationPermissions.isEmpty()) { + mPackageManager.addOnPermissionsChangeListener(mOnPermissionsChangedListener); + } else { + mProxyAppToLocationPermissions.clear(); + } + + for (String proxApp : nfwLocationAccessProxyApps) { + mProxyAppToLocationPermissions.put(proxApp, hasLocationPermission(proxApp)); + } + + updateNfwLocationAccessProxyAppsInGnssHal(); + } + + // Represents NfwNotification structure in IGnssVisibilityControlCallback.hal + private static class NfwNotification { + private static final String KEY_PROTOCOL_STACK = "ProtocolStack"; + private static final String KEY_OTHER_PROTOCOL_STACK_NAME = "OtherProtocolStackName"; + private static final String KEY_REQUESTOR = "Requestor"; + private static final String KEY_REQUESTOR_ID = "RequestorId"; + private static final String KEY_RESPONSE_TYPE = "ResponseType"; + private static final String KEY_IN_EMERGENCY_MODE = "InEmergencyMode"; + private static final String KEY_IS_CACHED_LOCATION = "IsCachedLocation"; + + // This must match with NfwResponseType enum in IGnssVisibilityControlCallback.hal. + private static final byte NFW_RESPONSE_TYPE_REJECTED = 0; + private static final byte NFW_RESPONSE_TYPE_ACCEPTED_NO_LOCATION_PROVIDED = 1; + private static final byte NFW_RESPONSE_TYPE_ACCEPTED_LOCATION_PROVIDED = 2; + + private final String mProxyAppPackageName; + private final byte mProtocolStack; + private final String mOtherProtocolStackName; + private final byte mRequestor; + private final String mRequestorId; + private final byte mResponseType; + private final boolean mInEmergencyMode; + private final boolean mIsCachedLocation; + + NfwNotification(String proxyAppPackageName, byte protocolStack, + String otherProtocolStackName, byte requestor, String requestorId, + byte responseType, boolean inEmergencyMode, boolean isCachedLocation) { + mProxyAppPackageName = proxyAppPackageName; + mProtocolStack = protocolStack; + mOtherProtocolStackName = otherProtocolStackName; + mRequestor = requestor; + mRequestorId = requestorId; + mResponseType = responseType; + mInEmergencyMode = inEmergencyMode; + mIsCachedLocation = isCachedLocation; + } + + void copyFieldsToIntent(Intent intent) { + intent.putExtra(KEY_PROTOCOL_STACK, mProtocolStack); + if (!TextUtils.isEmpty(mOtherProtocolStackName)) { + intent.putExtra(KEY_OTHER_PROTOCOL_STACK_NAME, mOtherProtocolStackName); + } + intent.putExtra(KEY_REQUESTOR, mRequestor); + if (!TextUtils.isEmpty(mRequestorId)) { + intent.putExtra(KEY_REQUESTOR_ID, mRequestorId); + } + intent.putExtra(KEY_RESPONSE_TYPE, mResponseType); + intent.putExtra(KEY_IN_EMERGENCY_MODE, mInEmergencyMode); + if (mResponseType == NFW_RESPONSE_TYPE_ACCEPTED_LOCATION_PROVIDED) { + intent.putExtra(KEY_IS_CACHED_LOCATION, mIsCachedLocation); + } + } + + @SuppressLint("DefaultLocale") + public String toString() { + return String.format( + "[Notification] proxyAppPackageName: %s, protocolStack: %d" + + ", otherProtocolStackName: %s, requestor: %d, requestorId: %s" + + ", responseType: %d, inEmergencyMode: %b, isCachedLocation: %b", + mProxyAppPackageName, mProtocolStack, mOtherProtocolStackName, + mRequestor, mRequestorId, mResponseType, mInEmergencyMode, mIsCachedLocation); + } + + String getResponseTypeAsString() { + switch (mResponseType) { + case NFW_RESPONSE_TYPE_REJECTED: + return "REJECTED"; + case NFW_RESPONSE_TYPE_ACCEPTED_NO_LOCATION_PROVIDED: + return "ACCEPTED_NO_LOCATION_PROVIDED"; + case NFW_RESPONSE_TYPE_ACCEPTED_LOCATION_PROVIDED: + return "ACCEPTED_LOCATION_PROVIDED"; + default: + return "<Unknown>"; + } + } + } + + private void handlePermissionsChanged(int uid) { + if (mProxyAppToLocationPermissions.isEmpty()) { + return; + } + + for (Map.Entry<String, Boolean> entry : mProxyAppToLocationPermissions.entrySet()) { + // Cannot cache uid since the application could be uninstalled and reinstalled. + final String proxyApp = entry.getKey(); + final Integer nfwProxyAppUid = getApplicationUid(proxyApp); + if (nfwProxyAppUid == null || nfwProxyAppUid != uid) { + continue; + } + + final boolean isLocationPermissionEnabled = hasLocationPermission(proxyApp); + if (isLocationPermissionEnabled != entry.getValue()) { + Log.i(TAG, "Location permission setting is changed to " + + (isLocationPermissionEnabled ? "enabled" : "disabled") + + " for non-framework location access proxy app " + + proxyApp); + entry.setValue(isLocationPermissionEnabled); + updateNfwLocationAccessProxyAppsInGnssHal(); + return; + } + } + } + + private Integer getApplicationUid(String pkgName) { + try { + return mPackageManager.getApplicationInfo(pkgName, 0).uid; + } catch (PackageManager.NameNotFoundException e) { + if (DEBUG) { + Log.d(TAG, "Non-framework location access proxy app " + + pkgName + " is not found."); + } + return null; + } + } + + private boolean hasLocationPermission(String pkgName) { + return mPackageManager.checkPermission(LOCATION_PERMISSION_NAME, pkgName) + == PackageManager.PERMISSION_GRANTED; + } + + private void updateNfwLocationAccessProxyAppsInGnssHal() { + // Get a count of proxy apps with location permission enabled to array creation size. + int countLocationPermissionEnabledProxyApps = 0; + for (Boolean hasLocationPermissionEnabled : mProxyAppToLocationPermissions.values()) { + if (hasLocationPermissionEnabled) { + ++countLocationPermissionEnabledProxyApps; + } + } + + int i = 0; + String[] locationPermissionEnabledProxyApps = + new String[countLocationPermissionEnabledProxyApps]; + for (Map.Entry<String, Boolean> entry : mProxyAppToLocationPermissions.entrySet()) { + final String proxyApp = entry.getKey(); + final boolean hasLocationPermissionEnabled = entry.getValue(); + if (hasLocationPermissionEnabled) { + locationPermissionEnabledProxyApps[i++] = proxyApp; + } + } + + String proxyAppsStr = Arrays.toString(locationPermissionEnabledProxyApps); + Log.i(TAG, "Updating non-framework location access proxy apps in the GNSS HAL to: " + + proxyAppsStr); + boolean result = native_enable_nfw_location_access(locationPermissionEnabledProxyApps); + if (!result) { + Log.e(TAG, "Failed to update non-framework location access proxy apps in the" + + " GNSS HAL to: " + proxyAppsStr); + } + } + + private void handleNfwNotification(NfwNotification nfwNotification) { + if (DEBUG) Log.d(TAG, nfwNotification.toString()); + + final String proxyAppPackageName = nfwNotification.mProxyAppPackageName; + if (TextUtils.isEmpty(proxyAppPackageName)) { + Log.e(TAG, "ProxyAppPackageName field is not set. Not sending intent to proxy app for " + + nfwNotification); + return; + } + + Boolean isLocationPermissionEnabled = mProxyAppToLocationPermissions.get( + proxyAppPackageName); + if (isLocationPermissionEnabled == null) { + // App is not in the configured list. + Log.e(TAG, "Could not find proxy app with name: " + proxyAppPackageName + " in the " + + "value specified for config parameter: " + + GnssConfiguration.CONFIG_NFW_PROXY_APPS + ". Not sending intent to proxy app" + + " for " + nfwNotification); + return; + } + + // Send intent to non-framework location proxy app with notification information. + final Intent intent = new Intent( + proxyAppPackageName + NFW_INTENT_ACTION_NFW_LOCATION_ACCESS_SUFFIX); + final String proxAppActivityName = + proxyAppPackageName + NFW_PROXY_APP_PKG_ACTIVITY_NAME_SUFFIX; + intent.setClassName(proxyAppPackageName, proxAppActivityName); + intent.setType(NFW_INTENT_TYPE); + nfwNotification.copyFieldsToIntent(intent); + + // Check if the proxy app is still installed. + final Integer clsAppUid = getApplicationUid(proxyAppPackageName); + if (clsAppUid == null || intent.resolveActivity(mPackageManager) == null) { + Log.i(TAG, "Proxy application " + proxyAppPackageName + " and/or activity " + + proxAppActivityName + " is not found. Not sending" + + " intent to proxy app for " + nfwNotification); + return; + } + + // Display location icon attributed to this proxy app. + mAppOps.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, clsAppUid, proxyAppPackageName); + + // Log proxy app permission mismatch between framework and GNSS HAL. + boolean isLocationRequestAccepted = + nfwNotification.mResponseType != NfwNotification.NFW_RESPONSE_TYPE_REJECTED; + if (isLocationPermissionEnabled != isLocationRequestAccepted) { + Log.w(TAG, "Permission mismatch. Framework proxy app " + proxyAppPackageName + + " location permission is set to " + isLocationPermissionEnabled + + " but GNSS non-framework location access response type is " + + nfwNotification.getResponseTypeAsString() + " for " + nfwNotification); + } + + // Notify proxy app. + try { + if (DEBUG) { + Log.d(TAG, "Sending non-framework location access notification intent: " + intent); + } + mContext.startActivityAsUser(intent, UserHandle.getUserHandleForUid(clsAppUid)); + } catch (ActivityNotFoundException e) { + Log.w(TAG, "Activity not found. Failed to send non-framework location access" + + " notification intent to proxy app activity: " + proxAppActivityName); + } + } + + private void postEvent(Runnable event) { + // Hold a wake lock until this message is delivered. + // Note that this assumes the message will not be removed from the queue before + // it is handled (otherwise the wake lock would be leaked). + mWakeLock.acquire(WAKELOCK_TIMEOUT_MILLIS); + if (!mHandler.post(runEventAndReleaseWakeLock(event))) { + mWakeLock.release(); + } + } + + private Runnable runEventAndReleaseWakeLock(Runnable event) { + return () -> { + try { + event.run(); + } finally { + mWakeLock.release(); + } + }; + } + + private native boolean native_enable_nfw_location_access(String[] proxyApps); +} diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index c8c5e8f136af..fb00aebb622f 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -107,6 +107,7 @@ cc_defaults { "android.hardware.gnss@1.0", "android.hardware.gnss@1.1", "android.hardware.gnss@2.0", + "android.hardware.gnss.visibility_control@1.0", "android.hardware.input.classifier@1.0", "android.hardware.ir@1.0", "android.hardware.light@2.0", diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp index b290bc516320..d618677fb513 100644 --- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp +++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp @@ -26,6 +26,7 @@ #include <android/hardware/gnss/1.1/IGnssMeasurement.h> #include <android/hardware/gnss/2.0/IGnssMeasurement.h> #include <android/hardware/gnss/measurement_corrections/1.0/IMeasurementCorrections.h> +#include <android/hardware/gnss/visibility_control/1.0/IGnssVisibilityControl.h> #include <nativehelper/JNIHelp.h> #include "jni.h" #include "hardware_legacy/power.h" @@ -88,6 +89,8 @@ static jmethodID method_correctionPlaneLatDeg; static jmethodID method_correctionPlaneLngDeg; static jmethodID method_correctionPlaneAltDeg; static jmethodID method_correctionPlaneAzimDeg; +static jmethodID method_reportNfwNotification; +static jmethodID method_isInEmergencySession; /* * Save a pointer to JavaVm to attach/detach threads executing @@ -152,6 +155,9 @@ using IAGnssCallback_V2_0 = android::hardware::gnss::V2_0::IAGnssCallback; using IMeasurementCorrections = android::hardware::gnss::measurement_corrections::V1_0::IMeasurementCorrections; +using android::hardware::gnss::visibility_control::V1_0::IGnssVisibilityControl; +using android::hardware::gnss::visibility_control::V1_0::IGnssVisibilityControlCallback; + struct GnssDeathRecipient : virtual public hidl_death_recipient { // hidl_death_recipient interface @@ -190,7 +196,7 @@ sp<IMeasurementCorrections> gnssCorrectionsIface = nullptr; // This boolean is needed to ensure that Gnsss Measurement Corrections related method are only // initalized when needed which will be few devices initially bool firstGnssMeasurementCorrectionInjected = false; - +sp<IGnssVisibilityControl> gnssVisibilityControlIface = nullptr; #define WAKE_LOCK_NAME "GPS" @@ -1080,6 +1086,54 @@ Return<void> GnssNiCallback::niNotifyCb( } /* + * GnssVisibilityControlCallback implements callback methods of IGnssVisibilityControlCallback.hal. + */ +struct GnssVisibilityControlCallback : public IGnssVisibilityControlCallback { + Return<void> nfwNotifyCb(const IGnssVisibilityControlCallback::NfwNotification& notification) + override; + Return<bool> isInEmergencySession() override; +}; + +Return<void> GnssVisibilityControlCallback::nfwNotifyCb( + const IGnssVisibilityControlCallback::NfwNotification& notification) { + JNIEnv* env = getJniEnv(); + jstring proxyAppPackageName = env->NewStringUTF(notification.proxyAppPackageName.c_str()); + jstring otherProtocolStackName = env->NewStringUTF(notification.otherProtocolStackName.c_str()); + jstring requestorId = env->NewStringUTF(notification.requestorId.c_str()); + + if (proxyAppPackageName && otherProtocolStackName && requestorId) { + env->CallVoidMethod(mCallbacksObj, method_reportNfwNotification, proxyAppPackageName, + notification.protocolStack, otherProtocolStackName, + notification.requestor, requestorId, + notification.inEmergencyMode, notification.isCachedLocation); + } else { + ALOGE("%s: OOM Error\n", __func__); + } + + if (requestorId) { + env->DeleteLocalRef(requestorId); + } + + if (otherProtocolStackName) { + env->DeleteLocalRef(otherProtocolStackName); + } + + if (proxyAppPackageName) { + env->DeleteLocalRef(proxyAppPackageName); + } + + checkAndClearExceptionFromCallback(env, __FUNCTION__); + return Void(); +} + +Return<bool> GnssVisibilityControlCallback::isInEmergencySession() { + JNIEnv* env = getJniEnv(); + auto result = env->CallBooleanMethod(mCallbacksObj, method_isInEmergencySession); + checkAndClearExceptionFromCallback(env, __FUNCTION__); + return result; +} + +/* * AGnssCallback_V1_0 implements callback methods required by the IAGnssCallback 1.0 interface. */ struct AGnssCallback_V1_0 : public IAGnssCallback_V1_0 { @@ -1313,6 +1367,9 @@ static void android_location_GnssLocationProvider_init_once(JNIEnv* env, jclass "reportLocationBatch", "([Landroid/location/Location;)V"); method_reportGnssServiceDied = env->GetMethodID(clazz, "reportGnssServiceDied", "()V"); + method_reportNfwNotification = env->GetMethodID(clazz, "reportNfwNotification", + "(Ljava/lang/String;BLjava/lang/String;BLjava/lang/String;BZZ)V"); + method_isInEmergencySession = env->GetMethodID(clazz, "isInEmergencySession", "()Z"); /* * Save a pointer to JVM. @@ -1388,12 +1445,6 @@ static void android_location_GnssLocationProvider_init_once(JNIEnv* env, jclass if (gnssHal_V2_0 != nullptr) { // TODO(b/119638366): getExtensionGnssMeasurement_1_1 from gnssHal_V2_0 auto gnssMeasurement = gnssHal_V2_0->getExtensionGnssMeasurement_2_0(); - auto gnssCorrections = gnssHal_V2_0->getExtensionMeasurementCorrections(); - if (!gnssCorrections.isOk()) { - ALOGD("Unable to get a handle to GnssMeasurementCorrections interface"); - } else { - gnssCorrectionsIface = gnssCorrections; - } if (!gnssMeasurement.isOk()) { ALOGD("Unable to get a handle to GnssMeasurement_V2_0"); } else { @@ -1401,6 +1452,12 @@ static void android_location_GnssLocationProvider_init_once(JNIEnv* env, jclass gnssMeasurementIface_V1_1 = gnssMeasurementIface_V2_0; gnssMeasurementIface = gnssMeasurementIface_V2_0; } + auto gnssCorrections = gnssHal_V2_0->getExtensionMeasurementCorrections(); + if (!gnssCorrections.isOk()) { + ALOGD("Unable to get a handle to GnssMeasurementCorrections interface"); + } else { + gnssCorrectionsIface = gnssCorrections; + } } else if (gnssHal_V1_1 != nullptr) { auto gnssMeasurement = gnssHal_V1_1->getExtensionGnssMeasurement_1_1(); if (!gnssMeasurement.isOk()) { @@ -1471,6 +1528,15 @@ static void android_location_GnssLocationProvider_init_once(JNIEnv* env, jclass } else { gnssBatchingIface = gnssBatching; } + + if (gnssHal_V2_0 != nullptr) { + auto gnssVisibilityControl = gnssHal_V2_0->getExtensionVisibilityControl(); + if (!gnssVisibilityControl.isOk()) { + ALOGD("Unable to get a handle to GnssVisibilityControl interface"); + } else { + gnssVisibilityControlIface = gnssVisibilityControl; + } + } } static jboolean android_location_GnssLocationProvider_is_supported( @@ -1572,7 +1638,13 @@ static jboolean android_location_GnssLocationProvider_init(JNIEnv* env, jobject if (agnssRilIface != nullptr) { agnssRilIface->setCallback(aGnssRilCbIface); } else { - ALOGI("Unable to Initialize AGnss Ril interface\n"); + ALOGI("Unable to initialize AGnss Ril interface\n"); + } + + if (gnssVisibilityControlIface != nullptr) { + sp<IGnssVisibilityControlCallback> gnssVisibilityControlCbIface = + new GnssVisibilityControlCallback(); + gnssVisibilityControlIface->setCallback(gnssVisibilityControlCbIface); } return JNI_TRUE; @@ -1974,6 +2046,11 @@ static jstring android_location_GnssLocationProvider_get_internal_state(JNIEnv* return result; } +static jboolean android_location_GnssLocationProvider_is_gnss_visibility_control_supported( + JNIEnv* /* env */, jclass /* clazz */) { + return (gnssVisibilityControlIface != nullptr) ? JNI_TRUE : JNI_FALSE; +} + static void android_location_GnssNetworkConnectivityHandler_update_network_state(JNIEnv* env, jobject /* obj */, jboolean connected, @@ -2557,6 +2634,29 @@ static jboolean android_location_GnssBatchingProvider_stop_batch(JNIEnv*, jclass return gnssBatchingIface->stop(); } +static jboolean android_location_GnssVisibilityControl_enable_nfw_location_access( + JNIEnv* env, jobject, jobjectArray proxyApps) { + if (gnssVisibilityControlIface == nullptr) { + ALOGI("No GNSS Visibility Control interface available"); + return JNI_FALSE; + } + + const jsize length = env->GetArrayLength(proxyApps); + hidl_vec<hidl_string> hidlProxyApps(length); + for (int i = 0; i < length; ++i) { + jstring proxyApp = (jstring) (env->GetObjectArrayElement(proxyApps, i)); + ScopedJniString jniProxyApp(env, proxyApp); + hidlProxyApps[i] = jniProxyApp; + } + + auto result = gnssVisibilityControlIface->enableNfwLocationAccess(hidlProxyApps); + if (result.isOk()) { + return result; + } else { + return JNI_FALSE; + } +} + static const JNINativeMethod sMethods[] = { /* name, signature, funcPtr */ {"class_init_native", "()V", reinterpret_cast<void *>( @@ -2607,6 +2707,8 @@ static const JNINativeMethod sMethods[] = { {"native_get_internal_state", "()Ljava/lang/String;", reinterpret_cast<void *>(android_location_GnssLocationProvider_get_internal_state)}, + {"native_is_gnss_visibility_control_supported", "()Z", reinterpret_cast<void *>( + android_location_GnssLocationProvider_is_gnss_visibility_control_supported)}, }; static const JNINativeMethod sMethodsBatching[] = { @@ -2736,6 +2838,14 @@ static const JNINativeMethod sConfigurationMethods[] = { reinterpret_cast<void *>(android_location_GnssConfiguration_set_es_extension_sec)}, }; +static const JNINativeMethod sVisibilityControlMethods[] = { + /* name, signature, funcPtr */ + {"native_enable_nfw_location_access", + "([Ljava/lang/String;)Z", + reinterpret_cast<void *>( + android_location_GnssVisibilityControl_enable_nfw_location_access)}, +}; + int register_android_server_location_GnssLocationProvider(JNIEnv* env) { jniRegisterNativeMethods( env, @@ -2767,6 +2877,11 @@ int register_android_server_location_GnssLocationProvider(JNIEnv* env) { "com/android/server/location/GnssConfiguration", sConfigurationMethods, NELEM(sConfigurationMethods)); + jniRegisterNativeMethods( + env, + "com/android/server/location/GnssVisibilityControl", + sVisibilityControlMethods, + NELEM(sVisibilityControlMethods)); return jniRegisterNativeMethods( env, "com/android/server/location/GnssLocationProvider", |