blob: 2d962acfe66559e5d6387728fbaa13689794c31c [file] [log] [blame]
/*
* Copyright (C) 2020 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.gnss.hal;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_LOCATION_HAS_ALTITUDE;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_LOCATION_HAS_BEARING;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_LOCATION_HAS_BEARING_ACCURACY;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_LOCATION_HAS_HORIZONTAL_ACCURACY;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_LOCATION_HAS_LAT_LONG;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_LOCATION_HAS_SPEED;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_LOCATION_HAS_SPEED_ACCURACY;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_LOCATION_HAS_VERTICAL_ACCURACY;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_REALTIME_HAS_TIMESTAMP_NS;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_REALTIME_HAS_TIME_UNCERTAINTY_NS;
import static com.android.server.location.gnss.hal.GnssNative.GeofenceCallbacks.GEOFENCE_STATUS_ERROR_ID_EXISTS;
import static com.android.server.location.gnss.hal.GnssNative.GeofenceCallbacks.GEOFENCE_STATUS_ERROR_ID_UNKNOWN;
import static com.android.server.location.gnss.hal.GnssNative.GeofenceCallbacks.GEOFENCE_STATUS_OPERATION_SUCCESS;
import static com.android.server.location.gnss.hal.GnssNative.GeofenceCallbacks.GEOFENCE_TRANSITION_ENTERED;
import static com.android.server.location.gnss.hal.GnssNative.GeofenceCallbacks.GEOFENCE_TRANSITION_EXITED;
import android.annotation.Nullable;
import android.location.GnssAntennaInfo;
import android.location.GnssMeasurementCorrections;
import android.location.GnssMeasurementsEvent;
import android.location.GnssNavigationMessage;
import android.location.Location;
import com.android.server.location.gnss.GnssPowerStats;
import com.android.server.location.gnss.hal.GnssNative.GnssLocationFlags;
import com.android.server.location.gnss.hal.GnssNative.GnssRealtimeFlags;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
/**
* Fake GNSS HAL for testing.
*/
public final class FakeGnssHal extends GnssNative.GnssHal {
public static class GnssHalPositionMode {
public final int Mode;
public final int Recurrence;
public final int MinInterval;
public final int PreferredAccuracy;
public final int PreferredTime;
public final boolean LowPowerMode;
GnssHalPositionMode() {
Mode = 0;
Recurrence = 0;
MinInterval = 0;
PreferredAccuracy = 0;
PreferredTime = 0;
LowPowerMode = false;
}
public GnssHalPositionMode(int mode, int recurrence, int minInterval, int preferredAccuracy,
int preferredTime, boolean lowPowerMode) {
Mode = mode;
Recurrence = recurrence;
MinInterval = minInterval;
PreferredAccuracy = preferredAccuracy;
PreferredTime = preferredTime;
LowPowerMode = lowPowerMode;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
GnssHalPositionMode that = (GnssHalPositionMode) o;
return Mode == that.Mode
&& Recurrence == that.Recurrence
&& MinInterval == that.MinInterval
&& PreferredAccuracy == that.PreferredAccuracy
&& PreferredTime == that.PreferredTime
&& LowPowerMode == that.LowPowerMode;
}
@Override
public int hashCode() {
return Objects.hash(Recurrence, MinInterval);
}
}
public static class GnssHalBatchingMode {
public final long PeriodNanos;
public final float MinUpdateDistanceMeters;
public final boolean WakeOnFifoFull;
GnssHalBatchingMode() {
PeriodNanos = 0;
MinUpdateDistanceMeters = 0.0f;
WakeOnFifoFull = false;
}
public GnssHalBatchingMode(long periodNanos, float minUpdateDistanceMeters,
boolean wakeOnFifoFull) {
PeriodNanos = periodNanos;
MinUpdateDistanceMeters = minUpdateDistanceMeters;
WakeOnFifoFull = wakeOnFifoFull;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
GnssHalBatchingMode that = (GnssHalBatchingMode) o;
return PeriodNanos == that.PeriodNanos
&& MinUpdateDistanceMeters == that.MinUpdateDistanceMeters
&& WakeOnFifoFull == that.WakeOnFifoFull;
}
@Override
public int hashCode() {
return Objects.hash(PeriodNanos, MinUpdateDistanceMeters, WakeOnFifoFull);
}
}
public static class GnssHalInjectedTime {
public final long Time;
public final long TimeReference;
public final int Uncertainty;
public GnssHalInjectedTime(long time, long timeReference, int uncertainty) {
Time = time;
TimeReference = timeReference;
Uncertainty = uncertainty;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
GnssHalInjectedTime that = (GnssHalInjectedTime) o;
return Time == that.Time
&& TimeReference == that.TimeReference
&& Uncertainty == that.Uncertainty;
}
@Override
public int hashCode() {
return Objects.hash(Time);
}
}
public static class GnssHalGeofence {
public final int GeofenceId;
public final Location Center;
public final double Radius;
public int LastTransition;
public int MonitorTransitions;
public final int NotificationResponsiveness;
public final int UnknownTimer;
public boolean Paused;
public GnssHalGeofence(int geofenceId, double latitude, double longitude, double radius,
int lastTransition, int monitorTransitions, int notificationResponsiveness,
int unknownTimer, boolean paused) {
GeofenceId = geofenceId;
Center = new Location("");
Center.setLatitude(latitude);
Center.setLongitude(longitude);
Radius = radius;
LastTransition = lastTransition;
MonitorTransitions = monitorTransitions;
NotificationResponsiveness = notificationResponsiveness;
UnknownTimer = unknownTimer;
Paused = paused;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
GnssHalGeofence that = (GnssHalGeofence) o;
return GeofenceId == that.GeofenceId
&& Double.compare(that.Radius, Radius) == 0
&& LastTransition == that.LastTransition
&& MonitorTransitions == that.MonitorTransitions
&& NotificationResponsiveness == that.NotificationResponsiveness
&& UnknownTimer == that.UnknownTimer
&& Paused == that.Paused
&& Center.equals(that.Center);
}
@Override
public int hashCode() {
return Objects.hash(GeofenceId);
}
}
private static class HalState {
private boolean mStarted = false;
private boolean mBatchingStarted = false;
private boolean mNavigationMessagesStarted = false;
private boolean mAntennaInfoListeningStarted = false;
private boolean mMeasurementCollectionStarted = false;
private boolean mMeasurementCollectionFullTracking = false;
private boolean mMeasurementCollectionCorrVecOutputsEnabled = false;
private int mMeasurementCollectionIntervalMillis = 0;
private GnssHalPositionMode mPositionMode = new GnssHalPositionMode();
private GnssHalBatchingMode mBatchingMode = new GnssHalBatchingMode();
private final ArrayList<Location> mBatchedLocations = new ArrayList<>();
private Location mInjectedLocation = null;
private Location mInjectedBestLocation = null;
private GnssHalInjectedTime mInjectedTime = null;
private GnssMeasurementCorrections mInjectedMeasurementCorrections = null;
private final HashMap<Integer, GnssHalGeofence> mGeofences = new HashMap<>();
private GnssPowerStats mPowerStats = new GnssPowerStats(0, 0, 0, 0, 0, 0, 0, 0,
new double[0]);
}
private @Nullable GnssNative mGnssNative;
private HalState mState = new HalState();
private boolean mIsNavigationMessageCollectionSupported = true;
private boolean mIsAntennaInfoListeningSupported = true;
private boolean mIsMeasurementSupported = true;
private boolean mIsMeasurementCorrectionsSupported = true;
private int mBatchSize = 0;
private boolean mIsGeofencingSupported = true;
private boolean mIsVisibilityControlSupported = true;
public FakeGnssHal() {}
public void restartHal() {
mState = new HalState();
Objects.requireNonNull(mGnssNative).restartHal();
}
public void setIsNavigationMessageCollectionSupported(boolean supported) {
mIsNavigationMessageCollectionSupported = supported;
}
public void setIsAntennaInfoListeningSupported(boolean supported) {
mIsAntennaInfoListeningSupported = supported;
}
public void setIsMeasurementSupported(boolean supported) {
mIsMeasurementSupported = supported;
}
public void setIsMeasurementCorrectionsSupported(boolean supported) {
mIsMeasurementCorrectionsSupported = supported;
}
public void setBatchSize(int batchSize) {
mBatchSize = batchSize;
}
public void setIsGeofencingSupported(boolean supported) {
mIsGeofencingSupported = supported;
}
public void setPowerStats(GnssPowerStats powerStats) {
mState.mPowerStats = powerStats;
}
public void setIsVisibilityControlSupported(boolean supported) {
mIsVisibilityControlSupported = supported;
}
public GnssHalPositionMode getPositionMode() {
return mState.mPositionMode;
}
public void reportLocation(Location location) {
if (mState.mStarted) {
Objects.requireNonNull(mGnssNative).reportLocation(true, location);
}
if (mState.mBatchingStarted) {
mState.mBatchedLocations.add(location);
if (mState.mBatchedLocations.size() >= mBatchSize) {
if (mState.mBatchingMode.WakeOnFifoFull) {
flushBatch();
} else {
mState.mBatchedLocations.remove(0);
}
}
}
for (GnssHalGeofence geofence : mState.mGeofences.values()) {
if (!geofence.Paused) {
if (geofence.Center.distanceTo(location) > geofence.Radius) {
if (geofence.LastTransition != GEOFENCE_TRANSITION_EXITED) {
geofence.LastTransition = GEOFENCE_TRANSITION_EXITED;
if ((geofence.MonitorTransitions & GEOFENCE_TRANSITION_EXITED) != 0) {
Objects.requireNonNull(mGnssNative).reportGeofenceTransition(
geofence.GeofenceId, location, GEOFENCE_TRANSITION_EXITED,
location.getTime());
}
}
} else {
if (geofence.LastTransition != GEOFENCE_TRANSITION_ENTERED) {
geofence.LastTransition = GEOFENCE_TRANSITION_ENTERED;
if ((geofence.MonitorTransitions & GEOFENCE_TRANSITION_ENTERED) != 0) {
Objects.requireNonNull(mGnssNative).reportGeofenceTransition(
geofence.GeofenceId, location, GEOFENCE_TRANSITION_ENTERED,
location.getTime());
}
}
}
}
}
}
public void reportNavigationMessage(GnssNavigationMessage message) {
if (mState.mNavigationMessagesStarted) {
Objects.requireNonNull(mGnssNative).reportNavigationMessage(message);
}
}
public void reportAntennaInfo(List<GnssAntennaInfo> antennaInfos) {
if (mState.mAntennaInfoListeningStarted) {
Objects.requireNonNull(mGnssNative).reportAntennaInfo(antennaInfos);
}
}
public boolean isMeasurementCollectionFullTracking() {
return mState.mMeasurementCollectionFullTracking;
}
public void reportMeasurement(GnssMeasurementsEvent event) {
if (mState.mMeasurementCollectionStarted) {
Objects.requireNonNull(mGnssNative).reportMeasurementData(event);
}
}
public GnssHalInjectedTime getLastInjectedTime() {
return mState.mInjectedTime;
}
public GnssMeasurementCorrections getLastInjectedCorrections() {
return mState.mInjectedMeasurementCorrections;
}
public Collection<GnssHalGeofence> getGeofences() {
return mState.mGeofences.values();
}
@Override
protected void classInitOnce() {}
@Override
protected boolean isSupported() {
return true;
}
@Override
protected void initOnce(GnssNative gnssNative, boolean reinitializeGnssServiceHandle) {
mGnssNative = Objects.requireNonNull(gnssNative);
}
@Override
protected boolean init() {
return true;
}
@Override
protected void cleanup() {}
@Override
protected boolean start() {
mState.mStarted = true;
return true;
}
@Override
protected boolean stop() {
mState.mStarted = false;
return true;
}
@Override
protected boolean setPositionMode(int mode, int recurrence, int minInterval,
int preferredAccuracy, int preferredTime, boolean lowPowerMode) {
mState.mPositionMode = new GnssHalPositionMode(mode, recurrence, minInterval,
preferredAccuracy, preferredTime, lowPowerMode);
return true;
}
@Override
protected String getInternalState() {
return "DebugState";
}
@Override
protected void deleteAidingData(int flags) {}
@Override
protected int readNmea(byte[] buffer, int bufferSize) {
return 0;
}
@Override
protected void injectLocation(@GnssLocationFlags int gnssLocationFlags, double latitude,
double longitude, double altitude, float speed, float bearing, float horizontalAccuracy,
float verticalAccuracy, float speedAccuracy, float bearingAccuracy, long timestamp,
@GnssRealtimeFlags int elapsedRealtimeFlags, long elapsedRealtimeNanos,
double elapsedRealtimeUncertaintyNanos) {
mState.mInjectedLocation = new Location("injected");
mState.mInjectedLocation.setLatitude(latitude);
mState.mInjectedLocation.setLongitude(longitude);
mState.mInjectedLocation.setAccuracy(horizontalAccuracy);
}
@Override
protected void injectBestLocation(@GnssLocationFlags int gnssLocationFlags, double latitude,
double longitude, double altitude, float speed, float bearing, float horizontalAccuracy,
float verticalAccuracy, float speedAccuracy, float bearingAccuracy, long timestamp,
@GnssRealtimeFlags int elapsedRealtimeFlags, long elapsedRealtimeNanos,
double elapsedRealtimeUncertaintyNanos) {
mState.mInjectedBestLocation = new Location("injectedBest");
if ((gnssLocationFlags & GNSS_LOCATION_HAS_LAT_LONG) != 0) {
mState.mInjectedBestLocation.setLatitude(latitude);
mState.mInjectedBestLocation.setLongitude(longitude);
}
if ((gnssLocationFlags & GNSS_LOCATION_HAS_ALTITUDE) != 0) {
mState.mInjectedBestLocation.setAltitude(altitude);
}
if ((gnssLocationFlags & GNSS_LOCATION_HAS_SPEED) != 0) {
mState.mInjectedBestLocation.setSpeed(speed);
}
if ((gnssLocationFlags & GNSS_LOCATION_HAS_BEARING) != 0) {
mState.mInjectedBestLocation.setBearing(bearing);
}
if ((gnssLocationFlags & GNSS_LOCATION_HAS_HORIZONTAL_ACCURACY) != 0) {
mState.mInjectedBestLocation.setAccuracy(horizontalAccuracy);
}
if ((gnssLocationFlags & GNSS_LOCATION_HAS_VERTICAL_ACCURACY) != 0) {
mState.mInjectedBestLocation.setVerticalAccuracyMeters(verticalAccuracy);
}
if ((gnssLocationFlags & GNSS_LOCATION_HAS_SPEED_ACCURACY) != 0) {
mState.mInjectedBestLocation.setSpeedAccuracyMetersPerSecond(speedAccuracy);
}
if ((gnssLocationFlags & GNSS_LOCATION_HAS_BEARING_ACCURACY) != 0) {
mState.mInjectedBestLocation.setBearingAccuracyDegrees(bearingAccuracy);
}
mState.mInjectedBestLocation.setTime(timestamp);
if ((elapsedRealtimeFlags & GNSS_REALTIME_HAS_TIMESTAMP_NS) != 0) {
mState.mInjectedBestLocation.setElapsedRealtimeNanos(elapsedRealtimeNanos);
}
if ((elapsedRealtimeFlags & GNSS_REALTIME_HAS_TIME_UNCERTAINTY_NS) != 0) {
mState.mInjectedBestLocation.setElapsedRealtimeUncertaintyNanos(
elapsedRealtimeUncertaintyNanos);
}
}
@Override
protected void injectTime(long time, long timeReference, int uncertainty) {
mState.mInjectedTime = new GnssHalInjectedTime(time, timeReference, uncertainty);
}
@Override
protected boolean isNavigationMessageCollectionSupported() {
return mIsNavigationMessageCollectionSupported;
}
@Override
protected boolean startNavigationMessageCollection() {
mState.mNavigationMessagesStarted = true;
return true;
}
@Override
protected boolean stopNavigationMessageCollection() {
mState.mNavigationMessagesStarted = false;
return true;
}
@Override
protected boolean isAntennaInfoSupported() {
return mIsAntennaInfoListeningSupported;
}
@Override
protected boolean startAntennaInfoListening() {
mState.mAntennaInfoListeningStarted = true;
return true;
}
@Override
protected boolean stopAntennaInfoListening() {
mState.mAntennaInfoListeningStarted = false;
return true;
}
@Override
protected boolean isMeasurementSupported() {
return mIsMeasurementSupported;
}
@Override
protected boolean startMeasurementCollection(boolean enableFullTracking,
boolean enableCorrVecOutputs, int intervalMillis) {
mState.mMeasurementCollectionStarted = true;
mState.mMeasurementCollectionFullTracking = enableFullTracking;
mState.mMeasurementCollectionCorrVecOutputsEnabled = enableCorrVecOutputs;
mState.mMeasurementCollectionIntervalMillis = intervalMillis;
return true;
}
@Override
protected boolean stopMeasurementCollection() {
mState.mMeasurementCollectionStarted = false;
mState.mMeasurementCollectionFullTracking = false;
mState.mMeasurementCollectionCorrVecOutputsEnabled = false;
mState.mMeasurementCollectionIntervalMillis = 0;
return true;
}
@Override
protected boolean isMeasurementCorrectionsSupported() {
return mIsMeasurementCorrectionsSupported;
}
@Override
protected boolean injectMeasurementCorrections(GnssMeasurementCorrections corrections) {
mState.mInjectedMeasurementCorrections = corrections;
return true;
}
@Override
protected boolean startSvStatusCollection() {
return true;
}
@Override
protected boolean stopSvStatusCollection() {
return true;
}
@Override
public boolean startNmeaMessageCollection() {
return true;
}
@Override
public boolean stopNmeaMessageCollection() {
return true;
}
@Override
protected int getBatchSize() {
return mBatchSize;
}
@Override
protected boolean initBatching() {
return true;
}
@Override
protected void cleanupBatching() {}
@Override
protected boolean startBatch(long periodNanos, float minUpdateDistanceMeters,
boolean wakeOnFifoFull) {
mState.mBatchingStarted = true;
mState.mBatchingMode = new GnssHalBatchingMode(periodNanos, minUpdateDistanceMeters,
wakeOnFifoFull);
return true;
}
@Override
protected void flushBatch() {
Location[] locations = mState.mBatchedLocations.toArray(new Location[0]);
mState.mBatchedLocations.clear();
Objects.requireNonNull(mGnssNative).reportLocationBatch(locations);
}
@Override
protected void stopBatch() {
mState.mBatchingStarted = false;
mState.mBatchingMode = new GnssHalBatchingMode();
mState.mBatchedLocations.clear();
}
@Override
protected boolean isGeofencingSupported() {
return mIsGeofencingSupported;
}
@Override
protected boolean addGeofence(int geofenceId, double latitude, double longitude, double radius,
int lastTransition, int monitorTransitions, int notificationResponsiveness,
int unknownTimer) {
if (mState.mGeofences.containsKey(geofenceId)) {
Objects.requireNonNull(mGnssNative).reportGeofenceAddStatus(geofenceId,
GEOFENCE_STATUS_ERROR_ID_EXISTS);
} else {
mState.mGeofences.put(geofenceId,
new GnssHalGeofence(geofenceId, latitude, longitude, radius, lastTransition,
monitorTransitions, notificationResponsiveness, unknownTimer, false));
Objects.requireNonNull(mGnssNative).reportGeofenceAddStatus(geofenceId,
GEOFENCE_STATUS_OPERATION_SUCCESS);
}
return true;
}
@Override
protected boolean resumeGeofence(int geofenceId, int monitorTransitions) {
GnssHalGeofence geofence = mState.mGeofences.get(geofenceId);
if (geofence != null) {
geofence.Paused = false;
geofence.MonitorTransitions = monitorTransitions;
Objects.requireNonNull(mGnssNative).reportGeofenceAddStatus(geofenceId,
GEOFENCE_STATUS_OPERATION_SUCCESS);
} else {
Objects.requireNonNull(mGnssNative).reportGeofenceAddStatus(geofenceId,
GEOFENCE_STATUS_ERROR_ID_UNKNOWN);
}
return true;
}
@Override
protected boolean pauseGeofence(int geofenceId) {
GnssHalGeofence geofence = mState.mGeofences.get(geofenceId);
if (geofence != null) {
geofence.Paused = true;
Objects.requireNonNull(mGnssNative).reportGeofenceAddStatus(geofenceId,
GEOFENCE_STATUS_OPERATION_SUCCESS);
} else {
Objects.requireNonNull(mGnssNative).reportGeofenceAddStatus(geofenceId,
GEOFENCE_STATUS_ERROR_ID_UNKNOWN);
}
return true;
}
@Override
protected boolean removeGeofence(int geofenceId) {
if (mState.mGeofences.remove(geofenceId) != null) {
Objects.requireNonNull(mGnssNative).reportGeofenceRemoveStatus(geofenceId,
GEOFENCE_STATUS_OPERATION_SUCCESS);
} else {
Objects.requireNonNull(mGnssNative).reportGeofenceRemoveStatus(geofenceId,
GEOFENCE_STATUS_ERROR_ID_UNKNOWN);
}
return true;
}
@Override
protected boolean isGnssVisibilityControlSupported() {
return mIsVisibilityControlSupported;
}
@Override
protected void sendNiResponse(int notificationId, int userResponse) {}
@Override
protected void requestPowerStats() {
Objects.requireNonNull(mGnssNative).reportGnssPowerStats(mState.mPowerStats);
}
@Override
protected void setAgpsServer(int type, String hostname, int port) {}
@Override
protected void setAgpsSetId(int type, String setId) {}
@Override
protected void setAgpsReferenceLocationCellId(int type, int mcc, int mnc, int lac, long cid,
int tac, int pcid, int arfcn) {}
@Override
protected boolean isPsdsSupported() {
return true;
}
@Override
protected void injectPsdsData(byte[] data, int length, int psdsType) {}
}