Local geomagnetic field update to sensor
Local geomagnetic field strength, declination, inclination information
is useful for magnetometer calibration. It also benefits rotation
vector sensor implementation as it gives a baseline that aids detecting
magnetic field disturbance.
Bug: 30958130
Test: Tested with marlin. Modified hal implementation can get local
geomagnetic field.
Change-Id: I373fe74d5a091a3adb80ff3c61e441edcf5a253b
diff --git a/services/core/java/com/android/server/SensorNotificationService.java b/services/core/java/com/android/server/SensorNotificationService.java
index 0610464..7f5befa 100644
--- a/services/core/java/com/android/server/SensorNotificationService.java
+++ b/services/core/java/com/android/server/SensorNotificationService.java
@@ -20,25 +20,46 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.hardware.GeomagneticField;
import android.hardware.Sensor;
+import android.hardware.SensorAdditionalInfo;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.os.Bundle;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Slog;
-public class SensorNotificationService extends SystemService implements SensorEventListener {
- //TODO: set DBG to false or remove Slog before release
- private static final boolean DBG = true;
+public class SensorNotificationService extends SystemService
+ implements SensorEventListener, LocationListener {
+ private static final boolean DBG = false;
private static final String TAG = "SensorNotificationService";
- private Context mContext;
+ private static final long MINUTE_IN_MS = 60 * 1000;
+ private static final long KM_IN_M = 1000;
+
+ private static final long LOCATION_MIN_TIME = 30 * MINUTE_IN_MS;
+ private static final long LOCATION_MIN_DISTANCE = 100 * KM_IN_M;
+
+ private static final String PROPERTY_USE_MOCKED_LOCATION =
+ "sensor.notification.use_mocked"; // max key length is 32
+
+ private static final long MILLIS_2010_1_1 = 1262358000000l;
+
+ private Context mContext;
private SensorManager mSensorManager;
+ private LocationManager mLocationManager;
private Sensor mMetaSensor;
+ // for rate limiting
+ private long mLocalGeomagneticFieldUpdateTime = -LOCATION_MIN_TIME;
+
public SensorNotificationService(Context context) {
super(context);
mContext = context;
@@ -50,7 +71,6 @@
public void onBootPhase(int phase) {
if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
- // start
mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
mMetaSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_DYNAMIC_SENSOR_META);
if (mMetaSensor == null) {
@@ -60,13 +80,28 @@
SensorManager.SENSOR_DELAY_FASTEST);
}
}
+
+ if (phase == PHASE_BOOT_COMPLETED) {
+ // LocationManagerService is initialized after PHASE_THIRD_PARTY_APPS_CAN_START
+ mLocationManager =
+ (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
+ if (mLocationManager == null) {
+ if (DBG) Slog.d(TAG, "Cannot obtain location service.");
+ } else {
+ mLocationManager.requestLocationUpdates(
+ LocationManager.PASSIVE_PROVIDER,
+ LOCATION_MIN_TIME,
+ LOCATION_MIN_DISTANCE,
+ this);
+ }
+ }
}
private void broadcastDynamicSensorChanged() {
Intent i = new Intent(Intent.ACTION_DYNAMIC_SENSOR_CHANGED);
i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); // avoid waking up manifest receivers
mContext.sendBroadcastAsUser(i, UserHandle.ALL);
- if (DBG) Slog.d(TAG, "DYNS sent dynamic sensor broadcast");
+ if (DBG) Slog.d(TAG, "dynamic sensor broadcast sent");
}
@Override
@@ -77,8 +112,62 @@
}
@Override
- public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ public void onLocationChanged(Location location) {
+ if (DBG) Slog.d(TAG, String.format(
+ "Location is (%f, %f), h %f, acc %f, mocked %b",
+ location.getLatitude(), location.getLongitude(),
+ location.getAltitude(), location.getAccuracy(),
+ location.isFromMockProvider()));
+ // lat long == 0 usually means invalid location
+ if (location.getLatitude() == 0 && location.getLongitude() == 0) {
+ return;
+ }
+
+ // update too often, ignore
+ if (SystemClock.elapsedRealtime() - mLocalGeomagneticFieldUpdateTime < 10 * MINUTE_IN_MS) {
+ return;
+ }
+
+ long time = System.currentTimeMillis();
+ // Mocked location should not be used. Except in test, only use mocked location
+ // Wrong system clock also gives bad values so ignore as well.
+ if (useMockedLocation() == location.isFromMockProvider() || time < MILLIS_2010_1_1) {
+ return;
+ }
+
+ GeomagneticField field = new GeomagneticField(
+ (float) location.getLatitude(), (float) location.getLongitude(),
+ (float) location.getAltitude(), time);
+ if (DBG) Slog.d(TAG, String.format(
+ "Nominal mag field, norm %fuT, decline %f deg, incline %f deg",
+ field.getFieldStrength() / 1000, field.getDeclination(), field.getInclination()));
+
+ try {
+ SensorAdditionalInfo info = SensorAdditionalInfo.createLocalGeomagneticField(
+ field.getFieldStrength() / 1000, // convert from nT to uT
+ (float)(field.getDeclination() * Math.PI / 180), // from degree to rad
+ (float)(field.getInclination() * Math.PI / 180)); // from degree to rad
+ if (info != null) {
+ mSensorManager.setOperationParameter(info);
+ mLocalGeomagneticFieldUpdateTime = SystemClock.elapsedRealtime();
+ }
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, "Invalid local geomagnetic field, ignore.");
+ }
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {}
+ @Override
+ public void onStatusChanged(String provider, int status, Bundle extras) {}
+ @Override
+ public void onProviderEnabled(String provider) {}
+ @Override
+ public void onProviderDisabled(String provider) {}
+
+ private boolean useMockedLocation() {
+ return "false".equals(System.getProperty(PROPERTY_USE_MOCKED_LOCATION, "false"));
}
}