Merge "Implements GNSS satellite blacklist" into pi-dev

This commit is contained in:
TreeHugger Robot
2018-04-05 23:28:19 +00:00
committed by Android (Google) Code Review
8 changed files with 340 additions and 15 deletions

View File

@@ -12541,6 +12541,19 @@ public final class Settings {
*/ */
public static final String SWAP_ENABLED = "swap_enabled"; 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";
} }
/** /**

View File

@@ -451,6 +451,7 @@ message GlobalSettingsProto {
// If set to 1, {@link Secure#LOCATION_MODE} will be set to {@link // If set to 1, {@link Secure#LOCATION_MODE} will be set to {@link
// Secure#LOCATION_MODE_OFF} temporarily for all users. // Secure#LOCATION_MODE_OFF} temporarily for all users.
optional SettingProto global_kill_switch = 5 [ (android.privacy).dest = DEST_AUTOMATIC ]; 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; optional Location location = 69;

View File

@@ -239,6 +239,7 @@ public class SettingsBackupTest {
Settings.Global.GLOBAL_HTTP_PROXY_HOST, Settings.Global.GLOBAL_HTTP_PROXY_HOST,
Settings.Global.GLOBAL_HTTP_PROXY_PAC, Settings.Global.GLOBAL_HTTP_PROXY_PAC,
Settings.Global.GLOBAL_HTTP_PROXY_PORT, Settings.Global.GLOBAL_HTTP_PROXY_PORT,
Settings.Global.GNSS_SATELLITE_BLACKLIST,
Settings.Global.GPRS_REGISTER_CHECK_PERIOD_MS, Settings.Global.GPRS_REGISTER_CHECK_PERIOD_MS,
Settings.Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, Settings.Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
Settings.Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED, Settings.Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,

View File

@@ -742,6 +742,9 @@ class SettingsProtoDumpUtil {
dumpSetting(s, p, dumpSetting(s, p,
Settings.Global.LOCATION_GLOBAL_KILL_SWITCH, Settings.Global.LOCATION_GLOBAL_KILL_SWITCH,
GlobalSettingsProto.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); p.end(locationToken);
final long lpmToken = p.start(GlobalSettingsProto.LOW_POWER_MODE); final long lpmToken = p.start(GlobalSettingsProto.LOW_POWER_MODE);

View File

@@ -83,7 +83,11 @@ import com.android.internal.location.GpsNetInitiatedHandler.GpsNiNotification;
import com.android.internal.location.ProviderProperties; import com.android.internal.location.ProviderProperties;
import com.android.internal.location.ProviderRequest; import com.android.internal.location.ProviderRequest;
import com.android.internal.location.gnssmetrics.GnssMetrics; import com.android.internal.location.gnssmetrics.GnssMetrics;
import com.android.server.location.GnssSatelliteBlacklistHelper.GnssSatelliteBlacklistCallback;
import com.android.server.location.NtpTimeHelper.InjectNtpTimeCallback; import com.android.server.location.NtpTimeHelper.InjectNtpTimeCallback;
import libcore.io.IoUtils;
import java.io.File; import java.io.File;
import java.io.FileDescriptor; import java.io.FileDescriptor;
import java.io.FileInputStream; import java.io.FileInputStream;
@@ -99,14 +103,13 @@ import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Properties; import java.util.Properties;
import libcore.io.IoUtils;
/** /**
* A GNSS implementation of LocationProvider used by LocationManager. * A GNSS implementation of LocationProvider used by LocationManager.
* *
* {@hide} * {@hide}
*/ */
public class GnssLocationProvider implements LocationProviderInterface, InjectNtpTimeCallback { public class GnssLocationProvider implements LocationProviderInterface, InjectNtpTimeCallback,
GnssSatelliteBlacklistCallback {
private static final String TAG = "GnssLocationProvider"; 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 // current status
private int mStatus = LocationProvider.TEMPORARILY_UNAVAILABLE; private int mStatus = LocationProvider.TEMPORARILY_UNAVAILABLE;
@@ -411,6 +414,7 @@ public class GnssLocationProvider implements LocationProviderInterface, InjectNt
private final ILocationManager mILocationManager; private final ILocationManager mILocationManager;
private final LocationExtras mLocationExtras = new LocationExtras(); private final LocationExtras mLocationExtras = new LocationExtras();
private final GnssStatusListenerHelper mListenerHelper; private final GnssStatusListenerHelper mListenerHelper;
private final GnssSatelliteBlacklistHelper mGnssSatelliteBlacklistHelper;
private final GnssMeasurementsProvider mGnssMeasurementsProvider; private final GnssMeasurementsProvider mGnssMeasurementsProvider;
private final GnssNavigationMessageProvider mGnssNavigationMessageProvider; private final GnssNavigationMessageProvider mGnssNavigationMessageProvider;
private final LocationChangeListener mNetworkLocationListener = new NetworkLocationListener(); 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) { private void subscriptionOrSimChanged(Context context) {
if (DEBUG) Log.d(TAG, "received SIM related action: "); if (DEBUG) Log.d(TAG, "received SIM related action: ");
TelephonyManager phone = (TelephonyManager) TelephonyManager phone = (TelephonyManager)
@@ -869,7 +883,10 @@ public class GnssLocationProvider implements LocationProviderInterface, InjectNt
}; };
mGnssMetrics = new GnssMetrics(mBatteryStats); 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_emergency_supl_pdn(int emergencySuplPdn);
private static native boolean native_set_satellite_blacklist(int[] constellations, int[] svIds);
// GNSS Batching // GNSS Batching
private static native int native_get_batch_size(); private static native int native_get_batch_size();

View File

@@ -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<Integer> 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<Integer> parseSatelliteBlacklist(String blacklist) throws NumberFormatException {
String[] strings = blacklist.split(BLACKLIST_DELIMITER);
List<Integer> 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;
}
}

View File

@@ -81,6 +81,7 @@ using android::hardware::Return;
using android::hardware::Void; using android::hardware::Void;
using android::hardware::hidl_vec; using android::hardware::hidl_vec;
using android::hardware::hidl_death_recipient; 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::GnssLocation;
using android::hardware::gnss::V1_0::GnssLocationFlags; 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::IAGnssRilCallback;
using android::hardware::gnss::V1_0::IGnssBatching; using android::hardware::gnss::V1_0::IGnssBatching;
using android::hardware::gnss::V1_0::IGnssBatchingCallback; 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::IGnssDebug;
using android::hardware::gnss::V1_0::IGnssGeofenceCallback; using android::hardware::gnss::V1_0::IGnssGeofenceCallback;
using android::hardware::gnss::V1_0::IGnssGeofencing; 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_0 = android::hardware::gnss::V1_0::IGnss;
using IGnss_V1_1 = android::hardware::gnss::V1_1::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_0 = android::hardware::gnss::V1_0::IGnssMeasurement;
using IGnssMeasurement_V1_1 = android::hardware::gnss::V1_1::IGnssMeasurement; using IGnssMeasurement_V1_1 = android::hardware::gnss::V1_1::IGnssMeasurement;
using IGnssMeasurementCallback_V1_0 = android::hardware::gnss::V1_0::IGnssMeasurementCallback; using IGnssMeasurementCallback_V1_0 = android::hardware::gnss::V1_0::IGnssMeasurementCallback;
@@ -137,7 +139,8 @@ sp<IGnssGeofencing> gnssGeofencingIface = nullptr;
sp<IAGnss> agnssIface = nullptr; sp<IAGnss> agnssIface = nullptr;
sp<IGnssBatching> gnssBatchingIface = nullptr; sp<IGnssBatching> gnssBatchingIface = nullptr;
sp<IGnssDebug> gnssDebugIface = nullptr; sp<IGnssDebug> gnssDebugIface = nullptr;
sp<IGnssConfiguration> gnssConfigurationIface = nullptr; sp<IGnssConfiguration_V1_0> gnssConfigurationIface = nullptr;
sp<IGnssConfiguration_V1_1> gnssConfigurationIface_V1_1 = nullptr;
sp<IGnssNi> gnssNiIface = nullptr; sp<IGnssNi> gnssNiIface = nullptr;
sp<IGnssMeasurement_V1_0> gnssMeasurementIface = nullptr; sp<IGnssMeasurement_V1_0> gnssMeasurementIface = nullptr;
sp<IGnssMeasurement_V1_1> gnssMeasurementIface_V1_1 = nullptr; sp<IGnssMeasurement_V1_1> gnssMeasurementIface_V1_1 = nullptr;
@@ -1098,13 +1101,11 @@ struct GnssBatchingCallback : public IGnssBatchingCallback {
* Methods from ::android::hardware::gps::V1_0::IGnssBatchingCallback * Methods from ::android::hardware::gps::V1_0::IGnssBatchingCallback
* follow. * follow.
*/ */
Return<void> gnssLocationBatchCb( Return<void> gnssLocationBatchCb(const hidl_vec<GnssLocation> & locations)
const ::android::hardware::hidl_vec<GnssLocation> & locations)
override; override;
}; };
Return<void> GnssBatchingCallback::gnssLocationBatchCb( Return<void> GnssBatchingCallback::gnssLocationBatchCb(const hidl_vec<GnssLocation> & locations) {
const ::android::hardware::hidl_vec<GnssLocation> & locations) {
JNIEnv* env = getJniEnv(); JNIEnv* env = getJniEnv();
jobjectArray jLocations = env->NewObjectArray(locations.size(), jobjectArray jLocations = env->NewObjectArray(locations.size(),
@@ -1257,11 +1258,21 @@ static void android_location_GnssLocationProvider_init_once(JNIEnv* env, jclass
gnssNiIface = gnssNi; gnssNiIface = gnssNi;
} }
auto gnssConfiguration = gnssHal->getExtensionGnssConfiguration(); if (gnssHal_V1_1 != nullptr) {
if (!gnssConfiguration.isOk()) { auto gnssConfiguration = gnssHal_V1_1->getExtensionGnssConfiguration_1_1();
ALOGD("Unable to get a handle to GnssConfiguration"); if (!gnssConfiguration.isOk()) {
ALOGD("Unable to get a handle to GnssConfiguration");
} else {
gnssConfigurationIface_V1_1 = gnssConfiguration;
gnssConfigurationIface = gnssConfigurationIface_V1_1;
}
} else { } 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(); 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<IGnssConfiguration_V1_1::BlacklistedSource> sources;
sources.resize(length);
for (int i = 0; i < length; i++) {
sources[i].constellation = static_cast<GnssConstellationType>(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) { static jint android_location_GnssLocationProvider_get_batch_size(JNIEnv*, jclass) {
if (gnssBatchingIface == nullptr) { if (gnssBatchingIface == nullptr) {
return 0; // batching not supported, size = 0 return 0; // batching not supported, size = 0
@@ -2185,6 +2238,9 @@ static const JNINativeMethod sMethods[] = {
{"native_set_emergency_supl_pdn", {"native_set_emergency_supl_pdn",
"(I)Z", "(I)Z",
reinterpret_cast<void *>(android_location_GnssLocationProvider_set_emergency_supl_pdn)}, reinterpret_cast<void *>(android_location_GnssLocationProvider_set_emergency_supl_pdn)},
{"native_set_satellite_blacklist",
"([I[I)Z",
reinterpret_cast<void *>(android_location_GnssLocationProvider_set_satellite_blacklist)},
{"native_get_batch_size", {"native_get_batch_size",
"()I", "()I",
reinterpret_cast<void *>(android_location_GnssLocationProvider_get_batch_size)}, reinterpret_cast<void *>(android_location_GnssLocationProvider_get_batch_size)},

View File

@@ -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<int[]> constellationsCaptor = ArgumentCaptor.forClass(int[].class);
ArgumentCaptor<int[]> svIdsCaptor = ArgumentCaptor.forClass(int[].class);
verify(mCallback).onUpdateSatelliteBlacklist(constellationsCaptor.capture(),
svIdsCaptor.capture());
int[] constellations = constellationsCaptor.getValue();
int[] svIds = svIdsCaptor.getValue();
List<Integer> 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<ContentObserver> contentObservers =
Shadows.shadowOf(RuntimeEnvironment.application.getContentResolver())
.getContentObservers(Settings.Global.getUriFor(globalSetting));
assertThat(contentObservers).isNotEmpty();
contentObservers.iterator().next().onChange(false /* selfChange */);
}
}