diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index 7fab2b96ddbe1..64970fbda76b9 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -102,6 +102,7 @@ import com.android.server.location.GeofenceManager; import com.android.server.location.GeofenceProxy; import com.android.server.location.GnssBatchingProvider; import com.android.server.location.GnssLocationProvider; +import com.android.server.location.GnssMeasurementCorrectionsProvider; import com.android.server.location.GnssMeasurementsProvider; import com.android.server.location.GnssNavigationMessageProvider; import com.android.server.location.GnssStatusListenerHelper; @@ -195,6 +196,7 @@ public class LocationManagerService extends ILocationManager.Stub { private PassiveProvider mPassiveProvider; // track passive provider for special cases private LocationBlacklist mBlacklist; private GnssMeasurementsProvider mGnssMeasurementsProvider; + private GnssMeasurementCorrectionsProvider mGnssMeasurementCorrectionsProvider; private GnssNavigationMessageProvider mGnssNavigationMessageProvider; @GuardedBy("mLock") private String mLocationControllerExtraPackage; @@ -757,6 +759,8 @@ public class LocationManagerService extends ILocationManager.Stub { mGnssStatusProvider = gnssProvider.getGnssStatusProvider(); mNetInitiatedListener = gnssProvider.getNetInitiatedListener(); mGnssMeasurementsProvider = gnssProvider.getGnssMeasurementsProvider(); + mGnssMeasurementCorrectionsProvider = + gnssProvider.getGnssMeasurementCorrectionsProvider(); mGnssNavigationMessageProvider = gnssProvider.getGnssNavigationMessageProvider(); mGpsGeofenceProxy = gnssProvider.getGpsGeofenceProxy(); } @@ -2921,22 +2925,28 @@ public class LocationManagerService extends ILocationManager.Stub { mContext.enforceCallingPermission( android.Manifest.permission.LOCATION_HARDWARE, "Location Hardware permission not granted to inject GNSS measurement corrections."); - if (!hasGnssPermissions(packageName) || mGnssMeasurementsProvider == null) { + if (!hasGnssPermissions(packageName)) { Slog.e(TAG, "Can not inject GNSS corrections due to no permission."); - } else { - mGnssMeasurementsProvider.injectGnssMeasurementCorrections(measurementCorrections); + return; } + if (mGnssMeasurementCorrectionsProvider == null) { + Slog.e(TAG, "Can not inject GNSS corrections. GNSS measurement corrections provider " + + "not available."); + return; + } + mGnssMeasurementCorrectionsProvider.injectGnssMeasurementCorrections( + measurementCorrections); } @Override public int getGnssCapabilities(String packageName) { mContext.enforceCallingPermission( android.Manifest.permission.LOCATION_HARDWARE, - "Location Hardware permission not granted to obrain GNSS chipset capabilities."); - if (!hasGnssPermissions(packageName) || mGnssMeasurementsProvider == null) { + "Location Hardware permission not granted to obtain GNSS chipset capabilities."); + if (!hasGnssPermissions(packageName) || mGnssMeasurementCorrectionsProvider == null) { return -1; } - return mGnssMeasurementsProvider.getGnssCapabilities(); + return mGnssMeasurementCorrectionsProvider.getCapabilities(); } @Override diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java index c173d660803fb..8249999033a41 100644 --- a/services/core/java/com/android/server/location/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/GnssLocationProvider.java @@ -166,9 +166,15 @@ public class GnssLocationProvider extends AbstractLocationProvider implements private static final int GPS_CAPABILITY_MSA = 0x0000004; private static final int GPS_CAPABILITY_SINGLE_SHOT = 0x0000008; private static final int GPS_CAPABILITY_ON_DEMAND_TIME = 0x0000010; - private static final int GPS_CAPABILITY_GEOFENCING = 0x0000020; + + // The following three capability flags are removed in IGnssCallback.hal@2.0 and their values + // are marked reserved and not reused in 2.0 to avoid confusion with prior versions. + public static final int GPS_CAPABILITY_GEOFENCING = 0x0000020; public static final int GPS_CAPABILITY_MEASUREMENTS = 0x0000040; - private static final int GPS_CAPABILITY_NAV_MESSAGES = 0x0000080; + public static final int GPS_CAPABILITY_NAV_MESSAGES = 0x0000080; + + private static final int GPS_CAPABILITY_LOW_POWER_MODE = 0x0000100; + private static final int GPS_CAPABILITY_SATELLITE_BLACKLIST = 0x0000200; // The AGPS SUPL mode private static final int AGPS_SUPL_MODE_MSA = 0x02; @@ -333,7 +339,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements private boolean mStarted; // capabilities of the GPS engine - private int mEngineCapabilities; + private volatile int mEngineCapabilities; // true if XTRA is supported private boolean mSupportsXtra; @@ -372,6 +378,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements private final LocationExtras mLocationExtras = new LocationExtras(); private final GnssStatusListenerHelper mGnssStatusListenerHelper; private final GnssMeasurementsProvider mGnssMeasurementsProvider; + private final GnssMeasurementCorrectionsProvider mGnssMeasurementCorrectionsProvider; private final GnssNavigationMessageProvider mGnssNavigationMessageProvider; private final LocationChangeListener mNetworkLocationListener = new NetworkLocationListener(); private final LocationChangeListener mFusedLocationListener = new FusedLocationListener(); @@ -437,6 +444,10 @@ public class GnssLocationProvider extends AbstractLocationProvider implements return mGnssMeasurementsProvider; } + public GnssMeasurementCorrectionsProvider getGnssMeasurementCorrectionsProvider() { + return mGnssMeasurementCorrectionsProvider; + } + public GnssNavigationMessageProvider getGnssNavigationMessageProvider() { return mGnssNavigationMessageProvider; } @@ -627,6 +638,8 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } }; + mGnssMeasurementCorrectionsProvider = new GnssMeasurementCorrectionsProvider(mHandler); + mGnssNavigationMessageProvider = new GnssNavigationMessageProvider(mContext, mHandler) { @Override protected boolean isGpsEnabled() { @@ -1258,6 +1271,10 @@ public class GnssLocationProvider extends AbstractLocationProvider implements mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, now + mFixInterval, mWakeupIntent); } + public int getGnssCapabilities() { + return mEngineCapabilities; + } + private boolean hasCapability(int capability) { return ((mEngineCapabilities & capability) != 0); } @@ -1467,22 +1484,27 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } @NativeEntryPoint - private void setEngineCapabilities(final int capabilities) { + private void setEngineCapabilities(final int capabilities, boolean hasSubHalCapabilityFlags) { // send to handler thread for fast native return, and in-order handling - mHandler.post( - () -> { - mEngineCapabilities = capabilities; + mHandler.post(() -> { + mEngineCapabilities = capabilities; - if (hasCapability(GPS_CAPABILITY_ON_DEMAND_TIME)) { - mNtpTimeHelper.enablePeriodicTimeInjection(); - requestUtcTime(); - } + if (hasCapability(GPS_CAPABILITY_ON_DEMAND_TIME)) { + mNtpTimeHelper.enablePeriodicTimeInjection(); + requestUtcTime(); + } - mGnssMeasurementsProvider.onCapabilitiesUpdated(capabilities); - mGnssNavigationMessageProvider.onCapabilitiesUpdated( - hasCapability(GPS_CAPABILITY_NAV_MESSAGES)); - restartRequests(); - }); + mGnssMeasurementsProvider.onCapabilitiesUpdated(capabilities, hasSubHalCapabilityFlags); + mGnssNavigationMessageProvider.onCapabilitiesUpdated(capabilities, + hasSubHalCapabilityFlags); + restartRequests(); + }); + } + + @NativeEntryPoint + private void setMeasurementCorrectionsCapabilities(final int capabilities) { + mHandler.post(() -> mGnssMeasurementCorrectionsProvider.onCapabilitiesUpdated( + capabilities)); } private void restartRequests() { @@ -2122,7 +2144,23 @@ public class GnssLocationProvider extends AbstractLocationProvider implements if (hasCapability(GPS_CAPABILITY_GEOFENCING)) s.append("GEOFENCING "); if (hasCapability(GPS_CAPABILITY_MEASUREMENTS)) s.append("MEASUREMENTS "); if (hasCapability(GPS_CAPABILITY_NAV_MESSAGES)) s.append("NAV_MESSAGES "); + if (hasCapability(GPS_CAPABILITY_LOW_POWER_MODE)) s.append("LOW_POWER_MODE "); + if (hasCapability(GPS_CAPABILITY_SATELLITE_BLACKLIST)) s.append("SATELLITE_BLACKLIST "); s.append(")\n"); + if (mGnssGeofenceProvider.isHardwareGeofenceSupported()) { + s.append(" hasSubHal=GEOFENCING\n"); + } + if (mGnssMeasurementsProvider.isAvailableInPlatform()) { + s.append(" hasSubHal=MEASUREMENTS\n"); + } + if (mGnssNavigationMessageProvider.isAvailableInPlatform()) { + s.append(" hasSubHal=NAV_MESSAGES\n"); + } + if (mGnssMeasurementCorrectionsProvider.isAvailableInPlatform()) { + s.append(" hasSubHal=MEASUREMENT_CORRECTIONS ["); + s.append(mGnssMeasurementCorrectionsProvider.toStringCapabilities()); + s.append("]\n"); + } s.append(mGnssMetrics.dumpGnssMetricsAsText()); s.append(" native internal state: ").append(native_get_internal_state()); s.append("\n"); diff --git a/services/core/java/com/android/server/location/GnssMeasurementCorrectionsProvider.java b/services/core/java/com/android/server/location/GnssMeasurementCorrectionsProvider.java new file mode 100644 index 0000000000000..21627876a49e1 --- /dev/null +++ b/services/core/java/com/android/server/location/GnssMeasurementCorrectionsProvider.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2019 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; + +import android.location.GnssMeasurementCorrections; +import android.os.Handler; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * Manages GNSS measurement corrections. + * + *
Implements the framework side of the GNSS HAL interfaces {@code IMeasurementCorrections.hal}
+ * and {@code IMeasurementCorrectionsCallback.hal).
+ *
+ * @hide
+ */
+public class GnssMeasurementCorrectionsProvider {
+ private static final String TAG = "GnssMeasurementCorrectionsProvider";
+
+ // These must match with the Capabilities enum in IMeasurementCorrectionsCallback.hal.
+ private static final int CAPABILITY_LOS_SATS = 0x0000001;
+ private static final int CAPABILITY_EXCESS_PATH_LENGTH = 0x0000002;
+ private static final int CAPABILITY_REFLECTING_PLANE = 0x0000004;
+
+ private static final int INVALID_CAPABILITIES = 1 << 31;
+
+ private final Handler mHandler;
+ private final GnssMeasurementCorrectionsProviderNative mNative;
+ private volatile int mCapabilities = INVALID_CAPABILITIES;
+
+ GnssMeasurementCorrectionsProvider(Handler handler) {
+ this(handler, new GnssMeasurementCorrectionsProviderNative());
+ }
+
+ @VisibleForTesting
+ GnssMeasurementCorrectionsProvider(Handler handler,
+ GnssMeasurementCorrectionsProviderNative aNative) {
+ mHandler = handler;
+ mNative = aNative;
+ }
+
+ /**
+ * Returns {@code true} if the GNSS HAL implementation supports measurement corrections.
+ */
+ public boolean isAvailableInPlatform() {
+ return mNative.isMeasurementCorrectionsSupported();
+ }
+
+ /**
+ * Injects GNSS measurement corrections into the GNSS chipset.
+ *
+ * @param measurementCorrections a {@link GnssMeasurementCorrections} object with the GNSS
+ * measurement corrections to be injected into the GNSS chipset.
+ */
+ public void injectGnssMeasurementCorrections(
+ GnssMeasurementCorrections measurementCorrections) {
+ if (!isCapabilitiesReceived()) {
+ Log.w(TAG, "Failed to inject GNSS measurement corrections. Capabilities "
+ + "not received yet.");
+ return;
+ }
+ mHandler.post(() -> {
+ if (!mNative.injectGnssMeasurementCorrections(measurementCorrections)) {
+ Log.e(TAG, "Failure in injecting GNSS corrections.");
+ }
+ });
+ }
+
+ /** Handle measurement corrections capabilities update from the GNSS HAL implementation. */
+ void onCapabilitiesUpdated(int capabilities) {
+ if (hasCapability(capabilities, CAPABILITY_LOS_SATS) || hasCapability(capabilities,
+ CAPABILITY_EXCESS_PATH_LENGTH)) {
+ mCapabilities = capabilities;
+ } else {
+ Log.e(TAG, "Failed to set capabilities. Received capabilities 0x"
+ + Integer.toHexString(capabilities) + " does not contain the mandatory "
+ + "LOS_SATS or the EXCESS_PATH_LENGTH capability.");
+ }
+ }
+
+ /**
+ * Returns the measurement corrections specific capabilities of the GNSS HAL implementation.
+ */
+ public int getCapabilities() {
+ return mCapabilities;
+ }
+
+ /**
+ * Returns the string representation of the GNSS measurement capabilities.
+ */
+ String toStringCapabilities() {
+ final int capabilities = getCapabilities();
+ StringBuilder s = new StringBuilder();
+ s.append("mCapabilities=0x").append(Integer.toHexString(capabilities));
+ s.append(" ( ");
+ if (hasCapability(capabilities, CAPABILITY_LOS_SATS)) {
+ s.append("LOS_SATS ");
+ }
+ if (hasCapability(capabilities, CAPABILITY_EXCESS_PATH_LENGTH)) {
+ s.append("EXCESS_PATH_LENGTH ");
+ }
+ if (hasCapability(capabilities, CAPABILITY_REFLECTING_PLANE)) {
+ s.append("REFLECTING_PLANE ");
+ }
+ s.append(")");
+ return s.toString();
+ }
+
+ private boolean isCapabilitiesReceived() {
+ return mCapabilities != INVALID_CAPABILITIES;
+ }
+
+ private static boolean hasCapability(int halCapabilities, int capability) {
+ return (halCapabilities & capability) != 0;
+ }
+
+ @VisibleForTesting
+ static class GnssMeasurementCorrectionsProviderNative {
+ public boolean isMeasurementCorrectionsSupported() {
+ return native_is_measurement_corrections_supported();
+ }
+
+ public boolean injectGnssMeasurementCorrections(
+ GnssMeasurementCorrections measurementCorrections) {
+ return native_inject_gnss_measurement_corrections(measurementCorrections);
+ }
+ }
+
+ private static native boolean native_is_measurement_corrections_supported();
+
+ private static native boolean native_inject_gnss_measurement_corrections(
+ GnssMeasurementCorrections measurementCorrections);
+}
diff --git a/services/core/java/com/android/server/location/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/GnssMeasurementsProvider.java
index 1fc7192916dff..844735a02eeb8 100644
--- a/services/core/java/com/android/server/location/GnssMeasurementsProvider.java
+++ b/services/core/java/com/android/server/location/GnssMeasurementsProvider.java
@@ -11,13 +11,12 @@
* 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
+ * limitations under the License.
*/
package com.android.server.location;
import android.content.Context;
-import android.location.GnssMeasurementCorrections;
import android.location.GnssMeasurementsEvent;
import android.location.IGnssMeasurementsListener;
import android.os.Handler;
@@ -42,7 +41,6 @@ public abstract class GnssMeasurementsProvider
private boolean mIsCollectionStarted;
private boolean mEnableFullTracking;
- private int mGnssEngineCapabilities;
protected GnssMeasurementsProvider(Context context, Handler handler) {
this(context, handler, new GnssMeasurementProviderNative());
@@ -87,21 +85,6 @@ public abstract class GnssMeasurementsProvider
}
}
- /**
- * Injects GNSS measurement corrections into the GNSS chipset.
- *
- * @param measurementCorrections a {@link GnssMeasurementCorrections} object with the GNSS
- * measurement corrections to be injected into the GNSS chipset.
- */
- public void injectGnssMeasurementCorrections(
- GnssMeasurementCorrections measurementCorrections) {
- mHandler.post(() -> {
- if (!mNative.injectGnssMeasurementCorrections(measurementCorrections)) {
- Log.e(TAG, "Failure in injecting GNSS corrections.");
- }
- });
- }
-
@Override
protected void unregisterFromService() {
boolean stopped = mNative.stopMeasurementCollection();
@@ -121,20 +104,20 @@ public abstract class GnssMeasurementsProvider
});
}
- /** Updates the framework about the capabilities of the GNSS chipset */
- public void onCapabilitiesUpdated(int capabilities) {
- mGnssEngineCapabilities = capabilities;
- boolean isGnssMeasurementsSupported =
- (capabilities & GnssLocationProvider.GPS_CAPABILITY_MEASUREMENTS) != 0;
+ /** Handle GNSS capabilities update from the GNSS HAL implementation. */
+ public void onCapabilitiesUpdated(int capabilities, boolean hasSubHalCapabilityFlags) {
+ // The IGnssCallback.hal@2.0 removed sub-HAL capability flags from the Capabilities enum
+ // and instead uses the sub-HAL non-null handle returned from IGnss.hal@2.0 to indicate
+ // support. Therefore, the 'hasSubHalCapabilityFlags' parameter is needed to tell if the
+ // 'capabilities' parameter includes the sub-HAL capability flags or not. Old HALs
+ // which explicitly set the sub-HAL capability bits must continue to work.
+ final boolean isGnssMeasurementsSupported = hasSubHalCapabilityFlags
+ ? (capabilities & GnssLocationProvider.GPS_CAPABILITY_MEASUREMENTS) != 0
+ : mNative.isMeasurementSupported();
setSupported(isGnssMeasurementsSupported);
updateResult();
}
- /** Obtains the GNSS engine capabilities. */
- public int getGnssCapabilities() {
- return mGnssEngineCapabilities;
- }
-
public void onGpsEnabledChanged() {
tryUpdateRegistrationWithService();
updateResult();
@@ -195,11 +178,6 @@ public abstract class GnssMeasurementsProvider
public boolean stopMeasurementCollection() {
return native_stop_measurement_collection();
}
-
- public boolean injectGnssMeasurementCorrections(
- GnssMeasurementCorrections measurementCorrections) {
- return native_inject_gnss_measurement_corrections(measurementCorrections);
- }
}
private static native boolean native_is_measurement_supported();
@@ -207,7 +185,4 @@ public abstract class GnssMeasurementsProvider
private static native boolean native_start_measurement_collection(boolean enableFullTracking);
private static native boolean native_stop_measurement_collection();
-
- private static native boolean native_inject_gnss_measurement_corrections(
- GnssMeasurementCorrections measurementCorrections);
}
diff --git a/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java b/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java
index 80a3f9bd2aba2..7e8b599129c36 100644
--- a/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java
+++ b/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java
@@ -11,7 +11,7 @@
* 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
+ * limitations under the License.
*/
package com.android.server.location;
@@ -51,7 +51,6 @@ public abstract class GnssNavigationMessageProvider
mNative = aNative;
}
- // TODO(b/37460011): Use this with death recovery logic.
void resumeIfStarted() {
if (DEBUG) {
Log.d(TAG, "resumeIfStarted");
@@ -92,7 +91,16 @@ public abstract class GnssNavigationMessageProvider
);
}
- public void onCapabilitiesUpdated(boolean isGnssNavigationMessageSupported) {
+ /** Handle GNSS capabilities update from the GNSS HAL implementation */
+ public void onCapabilitiesUpdated(int capabilities, boolean hasSubHalCapabilityFlags) {
+ // The IGnssCallback.hal@2.0 removed sub-HAL capability flags from the Capabilities enum
+ // and instead uses the sub-HAL non-null handle returned from IGnss.hal@2.0 to indicate
+ // support. Therefore, the 'hasSubHalCapabilityFlags' parameter is needed to tell if the
+ // 'capabilities' parameter includes the sub-HAL capability flags or not. Old HALs
+ // which explicitly set the sub-HAL capability bits must continue to work.
+ final boolean isGnssNavigationMessageSupported = hasSubHalCapabilityFlags
+ ? (capabilities & GnssLocationProvider.GPS_CAPABILITY_NAV_MESSAGES) != 0
+ : mNative.isNavigationMessageSupported();
setSupported(isGnssNavigationMessageSupported);
updateResult();
}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 050a07965b9c5..00b815ae1bfa7 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -109,6 +109,7 @@ cc_defaults {
"android.hardware.gnss@1.0",
"android.hardware.gnss@1.1",
"android.hardware.gnss@2.0",
+ "android.hardware.gnss.measurement_corrections@1.0",
"android.hardware.gnss.visibility_control@1.0",
"android.hardware.input.classifier@1.0",
"android.hardware.ir@1.0",
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index caba3afed41b3..a4955f07801a5 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -71,6 +71,7 @@ static jmethodID method_reportMeasurementData;
static jmethodID method_reportNavigationMessages;
static jmethodID method_reportLocationBatch;
static jmethodID method_reportGnssServiceDied;
+static jmethodID method_setMeasurementCorrectionsCapabilities;
static jmethodID method_correctionsGetLatitudeDegrees;
static jmethodID method_correctionsGetLongitudeDegrees;
static jmethodID method_correctionsGetAltitudeMeters;
@@ -158,10 +159,9 @@ using IAGnss_V2_0 = android::hardware::gnss::V2_0::IAGnss;
using IAGnssCallback_V1_0 = android::hardware::gnss::V1_0::IAGnssCallback;
using IAGnssCallback_V2_0 = android::hardware::gnss::V2_0::IAGnssCallback;
-using IMeasurementCorrections =
- android::hardware::gnss::measurement_corrections::V1_0::IMeasurementCorrections;
-using GnssSingleSatCorrectionFlags =
- android::hardware::gnss::measurement_corrections::V1_0::GnssSingleSatCorrectionFlags;
+using android::hardware::gnss::measurement_corrections::V1_0::IMeasurementCorrections;
+using android::hardware::gnss::measurement_corrections::V1_0::IMeasurementCorrectionsCallback;
+using android::hardware::gnss::measurement_corrections::V1_0::GnssSingleSatCorrectionFlags;
using android::hardware::gnss::visibility_control::V1_0::IGnssVisibilityControl;
using android::hardware::gnss::visibility_control::V1_0::IGnssVisibilityControlCallback;
@@ -201,7 +201,6 @@ sp