From 66c7ea91b413c9d812047288efb343c16b23d865 Mon Sep 17 00:00:00 2001 From: Yu-Han Yang Date: Sun, 11 Mar 2018 17:17:15 -0700 Subject: [PATCH] Implements GNSS satellite blacklist Bug: 38269641 Test: m -j ROBOTEST_FILTER=GnssSatelliteBlacklistHelperTest RunFrameworksServicesRoboTests Test: atest SettingsBackupTest Test: Tested with adb on device Change-Id: Ifaa330bf74353ea5c8826f0000d1935258b8dbf2 --- core/java/android/provider/Settings.java | 13 ++ .../android/providers/settings/global.proto | 1 + .../android/provider/SettingsBackupTest.java | 1 + .../settings/SettingsProtoDumpUtil.java | 3 + .../server/location/GnssLocationProvider.java | 29 +++- .../GnssSatelliteBlacklistHelper.java | 102 ++++++++++++++ ...d_server_location_GnssLocationProvider.cpp | 76 ++++++++-- .../GnssSatelliteBlacklistHelperTest.java | 130 ++++++++++++++++++ 8 files changed, 340 insertions(+), 15 deletions(-) create mode 100644 services/core/java/com/android/server/location/GnssSatelliteBlacklistHelper.java create mode 100644 services/robotests/src/com/android/server/location/GnssSatelliteBlacklistHelperTest.java diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 98d8666ecd476..c1793443bfb31 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -12541,6 +12541,19 @@ public final class Settings { */ public static final String SWAP_ENABLED = "swap_enabled"; + /** + * Blacklist of GNSS satellites. + * + * This is a list of integers separated by commas to represent pairs of (constellation, + * svid). Thus, the number of integers should be even. + * + * E.g.: "3,0,5,24" denotes (constellation=3, svid=0) and (constellation=5, svid=24) are + * blacklisted. Note that svid=0 denotes all svids in the + * constellation are blacklisted. + * + * @hide + */ + public static final String GNSS_SATELLITE_BLACKLIST = "gnss_satellite_blacklist"; } /** diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto index b5303c8a4eec9..05686a06f9cb6 100644 --- a/core/proto/android/providers/settings/global.proto +++ b/core/proto/android/providers/settings/global.proto @@ -451,6 +451,7 @@ message GlobalSettingsProto { // If set to 1, {@link Secure#LOCATION_MODE} will be set to {@link // Secure#LOCATION_MODE_OFF} temporarily for all users. optional SettingProto global_kill_switch = 5 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto gnss_satellite_blacklist = 6 [ (android.privacy).dest = DEST_AUTOMATIC ]; } optional Location location = 69; diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index 002c9e2257cb1..b1936b959816f 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -239,6 +239,7 @@ public class SettingsBackupTest { Settings.Global.GLOBAL_HTTP_PROXY_HOST, Settings.Global.GLOBAL_HTTP_PROXY_PAC, Settings.Global.GLOBAL_HTTP_PROXY_PORT, + Settings.Global.GNSS_SATELLITE_BLACKLIST, Settings.Global.GPRS_REGISTER_CHECK_PERIOD_MS, Settings.Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, Settings.Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED, diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index f43e719d47a39..994f1f0100d22 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -742,6 +742,9 @@ class SettingsProtoDumpUtil { dumpSetting(s, p, Settings.Global.LOCATION_GLOBAL_KILL_SWITCH, GlobalSettingsProto.Location.GLOBAL_KILL_SWITCH); + dumpSetting(s, p, + Settings.Global.GNSS_SATELLITE_BLACKLIST, + GlobalSettingsProto.Location.GNSS_SATELLITE_BLACKLIST); p.end(locationToken); final long lpmToken = p.start(GlobalSettingsProto.LOW_POWER_MODE); diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java index 5ba738093c6da..58bca196ae36d 100644 --- a/services/core/java/com/android/server/location/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/GnssLocationProvider.java @@ -83,7 +83,11 @@ import com.android.internal.location.GpsNetInitiatedHandler.GpsNiNotification; import com.android.internal.location.ProviderProperties; import com.android.internal.location.ProviderRequest; import com.android.internal.location.gnssmetrics.GnssMetrics; +import com.android.server.location.GnssSatelliteBlacklistHelper.GnssSatelliteBlacklistCallback; import com.android.server.location.NtpTimeHelper.InjectNtpTimeCallback; + +import libcore.io.IoUtils; + import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; @@ -99,14 +103,13 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Properties; -import libcore.io.IoUtils; - /** * A GNSS implementation of LocationProvider used by LocationManager. * * {@hide} */ -public class GnssLocationProvider implements LocationProviderInterface, InjectNtpTimeCallback { +public class GnssLocationProvider implements LocationProviderInterface, InjectNtpTimeCallback, + GnssSatelliteBlacklistCallback { private static final String TAG = "GnssLocationProvider"; @@ -308,7 +311,7 @@ public class GnssLocationProvider implements LocationProviderInterface, InjectNt } } - private Object mLock = new Object(); + private final Object mLock = new Object(); // current status private int mStatus = LocationProvider.TEMPORARILY_UNAVAILABLE; @@ -411,6 +414,7 @@ public class GnssLocationProvider implements LocationProviderInterface, InjectNt private final ILocationManager mILocationManager; private final LocationExtras mLocationExtras = new LocationExtras(); private final GnssStatusListenerHelper mListenerHelper; + private final GnssSatelliteBlacklistHelper mGnssSatelliteBlacklistHelper; private final GnssMeasurementsProvider mGnssMeasurementsProvider; private final GnssNavigationMessageProvider mGnssNavigationMessageProvider; private final LocationChangeListener mNetworkLocationListener = new NetworkLocationListener(); @@ -577,6 +581,16 @@ public class GnssLocationProvider implements LocationProviderInterface, InjectNt } }; + /** + * Implements {@link GnssSatelliteBlacklistCallback#onUpdateSatelliteBlacklist}. + */ + @Override + public void onUpdateSatelliteBlacklist(int[] constellations, int[] svids) { + mHandler.post(()->{ + native_set_satellite_blacklist(constellations, svids); + }); + } + private void subscriptionOrSimChanged(Context context) { if (DEBUG) Log.d(TAG, "received SIM related action: "); TelephonyManager phone = (TelephonyManager) @@ -869,7 +883,10 @@ public class GnssLocationProvider implements LocationProviderInterface, InjectNt }; mGnssMetrics = new GnssMetrics(mBatteryStats); - mNtpTimeHelper = new NtpTimeHelper(mContext, Looper.myLooper(), this); + mNtpTimeHelper = new NtpTimeHelper(mContext, looper, this); + mGnssSatelliteBlacklistHelper = new GnssSatelliteBlacklistHelper(mContext, + looper, this); + mHandler.post(mGnssSatelliteBlacklistHelper::updateSatelliteBlacklist); } /** @@ -2900,6 +2917,8 @@ public class GnssLocationProvider implements LocationProviderInterface, InjectNt private static native boolean native_set_emergency_supl_pdn(int emergencySuplPdn); + private static native boolean native_set_satellite_blacklist(int[] constellations, int[] svIds); + // GNSS Batching private static native int native_get_batch_size(); diff --git a/services/core/java/com/android/server/location/GnssSatelliteBlacklistHelper.java b/services/core/java/com/android/server/location/GnssSatelliteBlacklistHelper.java new file mode 100644 index 0000000000000..77951aa70bb59 --- /dev/null +++ b/services/core/java/com/android/server/location/GnssSatelliteBlacklistHelper.java @@ -0,0 +1,102 @@ +package com.android.server.location; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.ArrayList; +import java.util.List; + +/** + * Detects blacklist change and updates the blacklist. + */ +class GnssSatelliteBlacklistHelper { + + private static final String TAG = "GnssBlacklistHelper"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private static final String BLACKLIST_DELIMITER = ","; + + private final Context mContext; + private final GnssSatelliteBlacklistCallback mCallback; + + interface GnssSatelliteBlacklistCallback { + void onUpdateSatelliteBlacklist(int[] constellations, int[] svids); + } + + GnssSatelliteBlacklistHelper(Context context, Looper looper, + GnssSatelliteBlacklistCallback callback) { + mContext = context; + mCallback = callback; + ContentObserver contentObserver = new ContentObserver(new Handler(looper)) { + @Override + public void onChange(boolean selfChange) { + updateSatelliteBlacklist(); + } + }; + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor( + Settings.Global.GNSS_SATELLITE_BLACKLIST), + true, + contentObserver, UserHandle.USER_ALL); + } + + void updateSatelliteBlacklist() { + ContentResolver resolver = mContext.getContentResolver(); + String blacklist = Settings.Global.getString( + resolver, + Settings.Global.GNSS_SATELLITE_BLACKLIST); + if (blacklist == null) { + blacklist = ""; + } + if (DEBUG) { + Log.d(TAG, String.format("Update GNSS satellite blacklist: %s", blacklist)); + } + + List blacklistValues; + try { + blacklistValues = parseSatelliteBlacklist(blacklist); + } catch (NumberFormatException e) { + Log.e(TAG, "Exception thrown when parsing blacklist string.", e); + return; + } + + if (blacklistValues.size() % 2 != 0) { + Log.e(TAG, "blacklist string has odd number of values." + + "Aborting updateSatelliteBlacklist"); + return; + } + + int length = blacklistValues.size() / 2; + int[] constellations = new int[length]; + int[] svids = new int[length]; + for (int i = 0; i < length; i++) { + constellations[i] = blacklistValues.get(i * 2); + svids[i] = blacklistValues.get(i * 2 + 1); + } + mCallback.onUpdateSatelliteBlacklist(constellations, svids); + } + + @VisibleForTesting + static List parseSatelliteBlacklist(String blacklist) throws NumberFormatException { + String[] strings = blacklist.split(BLACKLIST_DELIMITER); + List parsed = new ArrayList<>(strings.length); + for (String string : strings) { + string = string.trim(); + if (!"".equals(string)) { + int value = Integer.parseInt(string); + if (value < 0) { + throw new NumberFormatException("Negative value is invalid."); + } + parsed.add(value); + } + } + return parsed; + } +} diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp index 21fea1c910cb0..e18eee2676106 100644 --- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp +++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp @@ -81,6 +81,7 @@ using android::hardware::Return; using android::hardware::Void; using android::hardware::hidl_vec; using android::hardware::hidl_death_recipient; +using android::hardware::gnss::V1_0::GnssConstellationType; using android::hardware::gnss::V1_0::GnssLocation; using android::hardware::gnss::V1_0::GnssLocationFlags; @@ -91,7 +92,6 @@ using android::hardware::gnss::V1_0::IAGnssRil; using android::hardware::gnss::V1_0::IAGnssRilCallback; using android::hardware::gnss::V1_0::IGnssBatching; using android::hardware::gnss::V1_0::IGnssBatchingCallback; -using android::hardware::gnss::V1_0::IGnssConfiguration; using android::hardware::gnss::V1_0::IGnssDebug; using android::hardware::gnss::V1_0::IGnssGeofenceCallback; using android::hardware::gnss::V1_0::IGnssGeofencing; @@ -108,6 +108,8 @@ using android::hidl::base::V1_0::IBase; using IGnss_V1_0 = android::hardware::gnss::V1_0::IGnss; using IGnss_V1_1 = android::hardware::gnss::V1_1::IGnss; +using IGnssConfiguration_V1_0 = android::hardware::gnss::V1_0::IGnssConfiguration; +using IGnssConfiguration_V1_1 = android::hardware::gnss::V1_1::IGnssConfiguration; using IGnssMeasurement_V1_0 = android::hardware::gnss::V1_0::IGnssMeasurement; using IGnssMeasurement_V1_1 = android::hardware::gnss::V1_1::IGnssMeasurement; using IGnssMeasurementCallback_V1_0 = android::hardware::gnss::V1_0::IGnssMeasurementCallback; @@ -137,7 +139,8 @@ sp gnssGeofencingIface = nullptr; sp agnssIface = nullptr; sp gnssBatchingIface = nullptr; sp gnssDebugIface = nullptr; -sp gnssConfigurationIface = nullptr; +sp gnssConfigurationIface = nullptr; +sp gnssConfigurationIface_V1_1 = nullptr; sp gnssNiIface = nullptr; sp gnssMeasurementIface = nullptr; sp gnssMeasurementIface_V1_1 = nullptr; @@ -1098,13 +1101,11 @@ struct GnssBatchingCallback : public IGnssBatchingCallback { * Methods from ::android::hardware::gps::V1_0::IGnssBatchingCallback * follow. */ - Return gnssLocationBatchCb( - const ::android::hardware::hidl_vec & locations) + Return gnssLocationBatchCb(const hidl_vec & locations) override; }; -Return GnssBatchingCallback::gnssLocationBatchCb( - const ::android::hardware::hidl_vec & locations) { +Return GnssBatchingCallback::gnssLocationBatchCb(const hidl_vec & locations) { JNIEnv* env = getJniEnv(); jobjectArray jLocations = env->NewObjectArray(locations.size(), @@ -1257,11 +1258,21 @@ static void android_location_GnssLocationProvider_init_once(JNIEnv* env, jclass gnssNiIface = gnssNi; } - auto gnssConfiguration = gnssHal->getExtensionGnssConfiguration(); - if (!gnssConfiguration.isOk()) { - ALOGD("Unable to get a handle to GnssConfiguration"); + if (gnssHal_V1_1 != nullptr) { + auto gnssConfiguration = gnssHal_V1_1->getExtensionGnssConfiguration_1_1(); + if (!gnssConfiguration.isOk()) { + ALOGD("Unable to get a handle to GnssConfiguration"); + } else { + gnssConfigurationIface_V1_1 = gnssConfiguration; + gnssConfigurationIface = gnssConfigurationIface_V1_1; + } } else { - gnssConfigurationIface = gnssConfiguration; + auto gnssConfiguration_V1_0 = gnssHal->getExtensionGnssConfiguration(); + if (!gnssConfiguration_V1_0.isOk()) { + ALOGD("Unable to get a handle to GnssConfiguration"); + } else { + gnssConfigurationIface = gnssConfiguration_V1_0; + } } auto gnssGeofencing = gnssHal->getExtensionGnssGeofencing(); @@ -1997,6 +2008,48 @@ static jboolean android_location_GnssLocationProvider_set_gnss_pos_protocol_sele } } +static jboolean android_location_GnssLocationProvider_set_satellite_blacklist( + JNIEnv* env, jobject, jintArray constellations, jintArray sv_ids) { + if (gnssConfigurationIface_V1_1 == nullptr) { + ALOGI("No GNSS Satellite Blacklist interface available"); + return JNI_FALSE; + } + + jint *constellation_array = env->GetIntArrayElements(constellations, 0); + if (NULL == constellation_array) { + ALOGI("GetIntArrayElements returns NULL."); + return JNI_FALSE; + } + jsize length = env->GetArrayLength(constellations); + + jint *sv_id_array = env->GetIntArrayElements(sv_ids, 0); + if (NULL == sv_id_array) { + ALOGI("GetIntArrayElements returns NULL."); + return JNI_FALSE; + } + + if (length != env->GetArrayLength(sv_ids)) { + ALOGI("Lengths of constellations and sv_ids are inconsistent."); + return JNI_FALSE; + } + + hidl_vec sources; + sources.resize(length); + + for (int i = 0; i < length; i++) { + sources[i].constellation = static_cast(constellation_array[i]); + sources[i].svid = sv_id_array[i]; + } + + auto result = gnssConfigurationIface_V1_1->setBlacklist(sources); + if (result.isOk()) { + return result; + } else { + return JNI_FALSE; + } +} + + static jint android_location_GnssLocationProvider_get_batch_size(JNIEnv*, jclass) { if (gnssBatchingIface == nullptr) { return 0; // batching not supported, size = 0 @@ -2185,6 +2238,9 @@ static const JNINativeMethod sMethods[] = { {"native_set_emergency_supl_pdn", "(I)Z", reinterpret_cast(android_location_GnssLocationProvider_set_emergency_supl_pdn)}, + {"native_set_satellite_blacklist", + "([I[I)Z", + reinterpret_cast(android_location_GnssLocationProvider_set_satellite_blacklist)}, {"native_get_batch_size", "()I", reinterpret_cast(android_location_GnssLocationProvider_get_batch_size)}, diff --git a/services/robotests/src/com/android/server/location/GnssSatelliteBlacklistHelperTest.java b/services/robotests/src/com/android/server/location/GnssSatelliteBlacklistHelperTest.java new file mode 100644 index 0000000000000..d6f54460afbe5 --- /dev/null +++ b/services/robotests/src/com/android/server/location/GnssSatelliteBlacklistHelperTest.java @@ -0,0 +1,130 @@ +package com.android.server.location; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.os.Looper; +import android.platform.test.annotations.Presubmit; +import android.provider.Settings; + +import com.android.server.testing.FrameworkRobolectricTestRunner; +import com.android.server.testing.SystemLoaderPackages; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.Shadows; +import org.robolectric.annotation.Config; + +import java.util.Collection; +import java.util.List; + +/** + * Unit tests for {@link GnssSatelliteBlacklistHelper}. + */ +@RunWith(FrameworkRobolectricTestRunner.class) +@Config( + manifest = Config.NONE, + shadows = { + }, + sdk = 27 +) +@SystemLoaderPackages({"com.android.server.location"}) +@Presubmit +public class GnssSatelliteBlacklistHelperTest { + + private Context mContext; + private ContentResolver mContentResolver; + @Mock + private GnssSatelliteBlacklistHelper.GnssSatelliteBlacklistCallback mCallback; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mContentResolver = mContext.getContentResolver(); + new GnssSatelliteBlacklistHelper(mContext, Looper.myLooper(), mCallback); + } + + @Test + public void blacklistOf2Satellites_callbackIsCalled() { + String blacklist = "3,0,5,24"; + updateBlacklistAndVerifyCallbackIsCalled(blacklist); + } + + @Test + public void blacklistWithSpaces_callbackIsCalled() { + String blacklist = "3, 11"; + updateBlacklistAndVerifyCallbackIsCalled(blacklist); + } + + @Test + public void emptyBlacklist_callbackIsCalled() { + String blacklist = ""; + updateBlacklistAndVerifyCallbackIsCalled(blacklist); + } + + @Test + public void blacklistWithOddNumberOfValues_callbackIsNotCalled() { + String blacklist = "3,0,5"; + updateBlacklistAndNotifyContentObserver(blacklist); + verify(mCallback, never()).onUpdateSatelliteBlacklist(any(int[].class), any(int[].class)); + } + + @Test + public void blacklistWithNegativeValue_callbackIsNotCalled() { + String blacklist = "3,-11"; + updateBlacklistAndNotifyContentObserver(blacklist); + verify(mCallback, never()).onUpdateSatelliteBlacklist(any(int[].class), any(int[].class)); + } + + @Test + public void blacklistWithNonDigitCharacter_callbackIsNotCalled() { + String blacklist = "3,1a,5,11"; + updateBlacklistAndNotifyContentObserver(blacklist); + verify(mCallback, never()).onUpdateSatelliteBlacklist(any(int[].class), any(int[].class)); + } + + private void updateBlacklistAndNotifyContentObserver(String blacklist) { + Settings.Global.putString(mContentResolver, + Settings.Global.GNSS_SATELLITE_BLACKLIST, blacklist); + notifyContentObserverFor(Settings.Global.GNSS_SATELLITE_BLACKLIST); + } + + private void updateBlacklistAndVerifyCallbackIsCalled(String blacklist) { + updateBlacklistAndNotifyContentObserver(blacklist); + + ArgumentCaptor constellationsCaptor = ArgumentCaptor.forClass(int[].class); + ArgumentCaptor svIdsCaptor = ArgumentCaptor.forClass(int[].class); + verify(mCallback).onUpdateSatelliteBlacklist(constellationsCaptor.capture(), + svIdsCaptor.capture()); + + int[] constellations = constellationsCaptor.getValue(); + int[] svIds = svIdsCaptor.getValue(); + List values = GnssSatelliteBlacklistHelper.parseSatelliteBlacklist(blacklist); + assertThat(values.size()).isEqualTo(constellations.length * 2); + assertThat(svIds.length).isEqualTo(constellations.length); + for (int i = 0; i < constellations.length; i++) { + assertThat(constellations[i]).isEqualTo(values.get(i * 2)); + assertThat(svIds[i]).isEqualTo(values.get(i * 2 + 1)); + } + } + + private static void notifyContentObserverFor(String globalSetting) { + Collection contentObservers = + Shadows.shadowOf(RuntimeEnvironment.application.getContentResolver()) + .getContentObservers(Settings.Global.getUriFor(globalSetting)); + assertThat(contentObservers).isNotEmpty(); + contentObservers.iterator().next().onChange(false /* selfChange */); + } +}