/* * Copyright (C) 2016 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; import android.content.BroadcastReceiver; 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, LocationListener { private static final boolean DBG = false; private static final String TAG = "SensorNotificationService"; 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; } public void onStart() { LocalServices.addService(SensorNotificationService.class, this); } public void onBootPhase(int phase) { if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE); mMetaSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_DYNAMIC_SENSOR_META); if (mMetaSensor == null) { if (DBG) Slog.d(TAG, "Cannot obtain dynamic meta sensor, not supported."); } else { mSensorManager.registerListener(this, mMetaSensor, 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, "dynamic sensor broadcast sent"); } @Override public void onSensorChanged(SensorEvent event) { if (event.sensor == mMetaSensor) { broadcastDynamicSensorChanged(); } } @Override 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")); } }