Non-framework location access visibility and control (framework2)
Bug: 119560261
Test: Partial testing with cuttlefish and location proxy test app
Change-Id: Ib7f4e50c971858de82c927d7ecb8f2edc3361eb3
diff --git a/services/core/java/com/android/server/location/GnssConfiguration.java b/services/core/java/com/android/server/location/GnssConfiguration.java
index 29465ad..9e33943 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 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 @@
"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 @@
}
/**
+ * 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 269767a..91ba705 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 @@
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 @@
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 @@
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 @@
super(locationProviderManager);
mContext = context;
+ mLooper = looper;
// Create a wake lock
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
@@ -1834,6 +1840,27 @@
}
}
+ // 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 @@
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 @@
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 0000000..845aa9d
--- /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 c8c5e8f..fb00aeb 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -107,6 +107,7 @@
"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 b290bc5..d618677 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_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 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 @@
// 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 @@
}
/*
+ * 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 @@
"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 @@
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 @@
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 @@
} 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 @@
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 @@
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 @@
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 @@
{"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 @@
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 @@
"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",