From c1f935e3c03642ec5c306ee1edc24cca2377e7c0 Mon Sep 17 00:00:00 2001 From: Shuo Qian Date: Sat, 23 Nov 2019 19:09:36 -0800 Subject: [PATCH] Integrate Signal Threshold for 5G 1) Introduce default values for 5G Signal thresholds 2) Introduce parameter to control in 5G NR Signal Strength Level 3) Introduce SignalThresholdInfo to support Framework and HAL Test: Treehugger Bug: 135717625 Change-Id: I8a716e9fe445349a56bb33b6b8f581febb21552b Merged-In: I8a716e9fe445349a56bb33b6b8f581febb21552b (cherry picked from commit 7d7b2f1a44f89bb5ae3e525d2afd1a7f52c46729) --- api/current.txt | 1 - .../telephony/CarrierConfigManager.java | 43 ++- .../telephony/CellSignalStrengthNr.java | 167 +++++++++++- .../telephony/SignalThresholdInfo.java | 256 ++++++++++++++++++ 4 files changed, 449 insertions(+), 18 deletions(-) create mode 100644 telephony/java/android/telephony/SignalThresholdInfo.java diff --git a/api/current.txt b/api/current.txt index bad43c790050d..424c642b64810 100644 --- a/api/current.txt +++ b/api/current.txt @@ -44266,7 +44266,6 @@ package android.telephony { field public static final String KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_RSSNR_INT = "opportunistic_network_entry_threshold_rssnr_int"; field public static final String KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSRP_INT = "opportunistic_network_exit_threshold_rsrp_int"; field public static final String KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSSNR_INT = "opportunistic_network_exit_threshold_rssnr_int"; - field public static final String KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT = "parameters_use_for_5g_nr_signal_bar_int"; field public static final String KEY_PREFER_2G_BOOL = "prefer_2g_bool"; field public static final String KEY_PREVENT_CLIR_ACTIVATION_AND_DEACTIVATION_CODE_BOOL = "prevent_clir_activation_and_deactivation_code_bool"; field public static final String KEY_RADIO_RESTART_FAILURE_CAUSES_INT_ARRAY = "radio_restart_failure_causes_int_array"; diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 92c6e938d7272..bbde8c0a2b7d0 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -2323,7 +2323,7 @@ public class CarrierConfigManager { * Reference: 3GPP TS 38.215 * * 4 threshold integers must be within the boundaries [-20 dB, -3 dB], and the levels are: - * "NONE: [-23, threshold1]" + * "NONE: [-20, threshold1]" * "POOR: (threshold1, threshold2]" * "MODERATE: (threshold2, threshold3]" * "GOOD: (threshold3, threshold4]" @@ -2357,15 +2357,26 @@ public class CarrierConfigManager { /** * Bit-field integer to determine whether to use SS reference signal received power (SSRSRP), * SS reference signal received quality (SSRSRQ), or/and SS signal-to-noise and interference - * ratio (SSSINR) for the number of 5G NR signal bars. If multiple measures are set bit, the - * parameter whose value is smallest is used to indicate the signal bar. + * ratio (SSSINR) for the number of 5G NR signal bars and signal criteria reporting enabling. + * + *

If a measure is not set, signal criteria reporting from modem will not be triggered and + * not be used for calculating signal level. If multiple measures are set bit, the parameter + * whose value is smallest is used to indicate the signal level. * * SSRSRP = 1 << 0, * SSRSRQ = 1 << 1, * SSSINR = 1 << 2, * + * The value of this key must be bitwise OR of {@link CellSignalStrengthNr#USE_SSRSRP}, + * {@link CellSignalStrengthNr#USE_SSRSRQ}, {@link CellSignalStrengthNr#USE_SSSINR}. + * + * For example, if both SSRSRP and SSSINR are used, the value of key is 5 (1 << 0 | 1 << 2). + * If the key is invalid or not configured, a default value (SSRSRP = 1 << 0) will apply. + * * Reference: 3GPP TS 38.215, * 3GPP TS 38.133 10.1.16.1 + * + * @hide */ public static final String KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT = "parameters_use_for_5g_nr_signal_bar_int"; @@ -3692,6 +3703,32 @@ public class CarrierConfigManager { -95, /* SIGNAL_STRENGTH_GOOD */ -85 /* SIGNAL_STRENGTH_GREAT */ }); + sDefaults.putIntArray(KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY, + // Boundaries: [-140 dB, -44 dB] + new int[] { + -125, /* SIGNAL_STRENGTH_POOR */ + -115, /* SIGNAL_STRENGTH_MODERATE */ + -105, /* SIGNAL_STRENGTH_GOOD */ + -95, /* SIGNAL_STRENGTH_GREAT */ + }); + sDefaults.putIntArray(KEY_5G_NR_SSRSRQ_THRESHOLDS_INT_ARRAY, + // Boundaries: [-20 dB, -3 dB] + new int[] { + -14, /* SIGNAL_STRENGTH_POOR */ + -12, /* SIGNAL_STRENGTH_MODERATE */ + -10, /* SIGNAL_STRENGTH_GOOD */ + -8 /* SIGNAL_STRENGTH_GREAT */ + }); + sDefaults.putIntArray(KEY_5G_NR_SSSINR_THRESHOLDS_INT_ARRAY, + // Boundaries: [-23 dB, 40 dB] + new int[] { + -8, /* SIGNAL_STRENGTH_POOR */ + 0, /* SIGNAL_STRENGTH_MODERATE */ + 8, /* SIGNAL_STRENGTH_GOOD */ + 16 /* SIGNAL_STRENGTH_GREAT */ + }); + sDefaults.putInt(KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT, + CellSignalStrengthNr.USE_SSRSRP); sDefaults.putString(KEY_WCDMA_DEFAULT_SIGNAL_STRENGTH_MEASUREMENT_STRING, "rssi"); sDefaults.putBoolean(KEY_CONFIG_SHOW_ORIG_DIAL_STRING_FOR_CDMA_BOOL, false); sDefaults.putBoolean(KEY_SHOW_CALL_BLOCKING_DISABLED_NOTIFICATION_ALWAYS_BOOL, false); diff --git a/telephony/java/android/telephony/CellSignalStrengthNr.java b/telephony/java/android/telephony/CellSignalStrengthNr.java index f9b7f6dbc193a..f31fafe36508b 100644 --- a/telephony/java/android/telephony/CellSignalStrengthNr.java +++ b/telephony/java/android/telephony/CellSignalStrengthNr.java @@ -16,11 +16,15 @@ package android.telephony; +import android.annotation.IntDef; import android.annotation.IntRange; import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; import java.util.Objects; /** @@ -36,13 +40,67 @@ public final class CellSignalStrengthNr extends CellSignalStrength implements Pa private static final String TAG = "CellSignalStrengthNr"; + // Lifted from Default carrier configs and max range of SSRSRP + // Boundaries: [-140 dB, -44 dB] + private int[] mSsRsrpThresholds = new int[] { + -125, /* SIGNAL_STRENGTH_POOR */ + -115, /* SIGNAL_STRENGTH_MODERATE */ + -105, /* SIGNAL_STRENGTH_GOOD */ + -95, /* SIGNAL_STRENGTH_GREAT */ + }; + + // Lifted from Default carrier configs and max range of SSRSRQ + // Boundaries: [-20 dB, -3 dB] + private int[] mSsRsrqThresholds = new int[] { + -14, /* SIGNAL_STRENGTH_POOR */ + -12, /* SIGNAL_STRENGTH_MODERATE */ + -10, /* SIGNAL_STRENGTH_GOOD */ + -8 /* SIGNAL_STRENGTH_GREAT */ + }; + + // Lifted from Default carrier configs and max range of SSSINR + // Boundaries: [-23 dB, 40 dB] + private int[] mSsSinrThresholds = new int[] { + -8, /* SIGNAL_STRENGTH_POOR */ + 0, /* SIGNAL_STRENGTH_MODERATE */ + 8, /* SIGNAL_STRENGTH_GOOD */ + 16 /* SIGNAL_STRENGTH_GREAT */ + }; + /** - * These threshold values are copied from LTE. - * TODO: make it configurable via CarrierConfig. + * Indicates SSRSRP is considered for {@link #getLevel()} and reporting from modem. + * + * @hide */ - private static final int SIGNAL_GREAT_THRESHOLD = -95; - private static final int SIGNAL_GOOD_THRESHOLD = -105; - private static final int SIGNAL_MODERATE_THRESHOLD = -115; + public static final int USE_SSRSRP = 1 << 0; + /** + * Indicates SSRSRQ is considered for {@link #getLevel()} and reporting from modem. + * + * @hide + */ + public static final int USE_SSRSRQ = 1 << 1; + /** + * Indicates SSSINR is considered for {@link #getLevel()} and reporting from modem. + * + * @hide + */ + public static final int USE_SSSINR = 1 << 2; + + /** + * Bit-field integer to determine whether to use SS reference signal received power (SSRSRP), + * SS reference signal received quality (SSRSRQ), or/and SS signal-to-noise and interference + * ratio (SSSINR) for the number of 5G NR signal bars. If multiple measures are set bit, the + * parameter whose value is smallest is used to indicate the signal bar. + * + * @hide + */ + @IntDef(flag = true, prefix = { "USE_" }, value = { + USE_SSRSRP, + USE_SSRSRQ, + USE_SSSINR + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SignalLevelAndReportCriteriaSource {} private int mCsiRsrp; private int mCsiRsrq; @@ -52,6 +110,21 @@ public final class CellSignalStrengthNr extends CellSignalStrength implements Pa private int mSsSinr; private int mLevel; + /** + * Bit-field integer to determine whether to use SS reference signal received power (SSRSRP), + * SS reference signal received quality (SSRSRQ), or/and SS signal-to-noise and interference + * ratio (SSSINR) for the number of 5G NR signal bars. If multiple measures are set bit, the + * parameter whose value is smallest is used to indicate the signal bar. + * + * SSRSRP = 1 << 0, + * SSRSRQ = 1 << 1, + * SSSINR = 1 << 2, + * + * For example, if both SSRSRP and SSSINR are used, the value of key is 5 (1 << 0 | 1 << 2). + * If the key is invalid or not configured, a default value (SSRSRP = 1 << 0) will apply. + */ + private int mParametersUseForLevel; + /** @hide */ public CellSignalStrengthNr() { setDefaultValues(); @@ -182,6 +255,7 @@ public final class CellSignalStrengthNr extends CellSignalStrength implements Pa mSsRsrq = CellInfo.UNAVAILABLE; mSsSinr = CellInfo.UNAVAILABLE; mLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + mParametersUseForLevel = USE_SSRSRP; } /** {@inheritDoc} */ @@ -191,20 +265,83 @@ public final class CellSignalStrengthNr extends CellSignalStrength implements Pa return mLevel; } + /** + * Checks if the given parameter type is considered to use for {@link #getLevel()}. + * + * Note: if multiple parameter types are considered, the smaller level for one of the + * parameters would be returned by {@link #getLevel()} + * + * @param parameterType bitwise OR of {@link #USE_SSRSRP}, {@link #USE_SSRSRQ}, + * {@link #USE_SSSINR} + * @return {@code true} if the level is calculated based on the given parameter type; + * {@code false} otherwise. + * + */ + private boolean isLevelForParameter(@SignalLevelAndReportCriteriaSource int parameterType) { + return (parameterType & mParametersUseForLevel) == parameterType; + } + /** @hide */ @Override public void updateLevel(PersistableBundle cc, ServiceState ss) { - if (mSsRsrp == CellInfo.UNAVAILABLE) { - mLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; - } else if (mSsRsrp >= SIGNAL_GREAT_THRESHOLD) { - mLevel = SIGNAL_STRENGTH_GREAT; - } else if (mSsRsrp >= SIGNAL_GOOD_THRESHOLD) { - mLevel = SIGNAL_STRENGTH_GOOD; - } else if (mSsRsrp >= SIGNAL_MODERATE_THRESHOLD) { - mLevel = SIGNAL_STRENGTH_MODERATE; + if (cc == null) { + mParametersUseForLevel = USE_SSRSRP; } else { - mLevel = SIGNAL_STRENGTH_POOR; + mParametersUseForLevel = cc.getInt( + CarrierConfigManager.KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT, USE_SSRSRP); + Rlog.i(TAG, "Using SSRSRP for Level."); + mSsRsrpThresholds = cc.getIntArray( + CarrierConfigManager.KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY); + Rlog.i(TAG, "Applying 5G NR SSRSRP Thresholds: " + Arrays.toString(mSsRsrpThresholds)); + mSsRsrqThresholds = cc.getIntArray( + CarrierConfigManager.KEY_5G_NR_SSRSRQ_THRESHOLDS_INT_ARRAY); + Rlog.i(TAG, "Applying 5G NR SSRSRQ Thresholds: " + Arrays.toString(mSsRsrqThresholds)); + mSsSinrThresholds = cc.getIntArray( + CarrierConfigManager.KEY_5G_NR_SSSINR_THRESHOLDS_INT_ARRAY); + Rlog.i(TAG, "Applying 5G NR SSSINR Thresholds: " + Arrays.toString(mSsSinrThresholds)); } + int ssRsrpLevel = SignalStrength.INVALID; + int ssRsrqLevel = SignalStrength.INVALID; + int ssSinrLevel = SignalStrength.INVALID; + if (isLevelForParameter(USE_SSRSRP)) { + ssRsrpLevel = updateLevelWithMeasure(mSsRsrp, mSsRsrpThresholds); + Rlog.i(TAG, "Updated 5G NR SSRSRP Level: " + ssRsrpLevel); + } + if (isLevelForParameter(USE_SSRSRQ)) { + ssRsrqLevel = updateLevelWithMeasure(mSsRsrq, mSsRsrqThresholds); + Rlog.i(TAG, "Updated 5G NR SSRSRQ Level: " + ssRsrqLevel); + } + if (isLevelForParameter(USE_SSSINR)) { + ssSinrLevel = updateLevelWithMeasure(mSsSinr, mSsSinrThresholds); + Rlog.i(TAG, "Updated 5G NR SSSINR Level: " + ssSinrLevel); + } + // Apply the smaller value among three levels of three measures. + mLevel = Math.min(Math.min(ssRsrpLevel, ssRsrqLevel), ssSinrLevel); + } + + /** + * Update level with corresponding measure and thresholds. + * + * @param measure corresponding signal measure + * @param thresholds corresponding signal thresholds + * @return level of the signal strength + */ + private int updateLevelWithMeasure(int measure, int[] thresholds) { + int level; + if (measure == CellInfo.UNAVAILABLE) { + level = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + } else if (measure > thresholds[3]) { + level = SIGNAL_STRENGTH_GREAT; + } else if (measure > thresholds[2]) { + level = SIGNAL_STRENGTH_GOOD; + } else if (measure > thresholds[1]) { + level = SIGNAL_STRENGTH_MODERATE; + } else if (measure > thresholds[0]) { + level = SIGNAL_STRENGTH_POOR; + } else { + level = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + } + return level; } /** @@ -247,6 +384,7 @@ public final class CellSignalStrengthNr extends CellSignalStrength implements Pa mSsRsrq = s.mSsRsrq; mSsSinr = s.mSsSinr; mLevel = s.mLevel; + mParametersUseForLevel = s.mParametersUseForLevel; } /** @hide */ @@ -290,6 +428,7 @@ public final class CellSignalStrengthNr extends CellSignalStrength implements Pa .append(" ssRsrq = " + mSsRsrq) .append(" ssSinr = " + mSsSinr) .append(" level = " + mLevel) + .append(" parametersUseForLevel = " + mParametersUseForLevel) .append(" }") .toString(); } diff --git a/telephony/java/android/telephony/SignalThresholdInfo.java b/telephony/java/android/telephony/SignalThresholdInfo.java new file mode 100644 index 0000000000000..f6f6d75c37c62 --- /dev/null +++ b/telephony/java/android/telephony/SignalThresholdInfo.java @@ -0,0 +1,256 @@ +/* + * 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 android.telephony; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; +import java.util.Objects; + +/** + * Defines the threshold value of the signal strength. + * @hide + */ +public class SignalThresholdInfo implements Parcelable { + /** + * Received Signal Strength Indication. + * Range: -113 dBm and -51 dBm + * Used RAN: GERAN, CDMA2000 + * Reference: 3GPP TS 27.007 section 8.5. + */ + public static final int SIGNAL_RSSI = 1; + + /** + * Received Signal Code Power. + * Range: -120 dBm to -25 dBm; + * Used RAN: UTRAN + * Reference: 3GPP TS 25.123, section 9.1.1.1 + */ + public static final int SIGNAL_RSCP = 2; + + /** + * Reference Signal Received Power. + * Range: -140 dBm to -44 dBm; + * Used RAN: EUTRAN + * Reference: 3GPP TS 36.133 9.1.4 + */ + public static final int SIGNAL_RSRP = 3; + + /** + * Reference Signal Received Quality + * Range: -20 dB to -3 dB; + * Used RAN: EUTRAN + * Reference: 3GPP TS 36.133 9.1.7 + */ + public static final int SIGNAL_RSRQ = 4; + + /** + * Reference Signal Signal to Noise Ratio + * Range: -20 dB to 30 dB; + * Used RAN: EUTRAN + */ + public static final int SIGNAL_RSSNR = 5; + + /** + * 5G SS reference signal received power. + * Range: -140 dBm to -44 dBm. + * Used RAN: NGRAN + * Reference: 3GPP TS 38.215. + */ + public static final int SIGNAL_SSRSRP = 6; + + /** + * 5G SS reference signal received quality. + * Range: -20 dB to -3 dB. + * Used RAN: NGRAN + * Reference: 3GPP TS 38.215. + */ + public static final int SIGNAL_SSRSRQ = 7; + + /** + * 5G SS signal-to-noise and interference ratio. + * Range: -23 dB to 40 dB + * Used RAN: NGRAN + * Reference: 3GPP TS 38.215 section 5.1.*, 3GPP TS 38.133 section 10.1.16.1. + */ + public static final int SIGNAL_SSSINR = 8; + + /** @hide */ + @IntDef(prefix = { "SIGNAL_" }, value = { + SIGNAL_RSSI, + SIGNAL_RSCP, + SIGNAL_RSRP, + SIGNAL_RSRQ, + SIGNAL_RSSNR, + SIGNAL_SSRSRP, + SIGNAL_SSRSRQ, + SIGNAL_SSSINR + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SignalMeasurementType {} + + @SignalMeasurementType + private int mSignalMeasurement; + + /** + * A hysteresis time in milliseconds to prevent flapping. + * A value of 0 disables hysteresis + */ + private int mHysteresisMs; + + /** + * An interval in dB defining the required magnitude change between reports. + * hysteresisDb must be smaller than the smallest threshold delta. + * An interval value of 0 disables hysteresis. + */ + private int mHysteresisDb; + + /** + * List of threshold values. + * Range and unit must reference specific SignalMeasurementType + * The threshold values for which to apply criteria. + * A vector size of 0 disables the use of thresholds for reporting. + */ + private int[] mThresholds = null; + + /** + * {@code true} means modem must trigger the report based on the criteria; + * {@code false} means modem must not trigger the report based on the criteria. + */ + private boolean mIsEnabled = true; + + /** + * Indicates the hysteresisMs is disabled. + */ + public static final int HYSTERESIS_MS_DISABLED = 0; + + /** + * Indicates the hysteresisDb is disabled. + */ + public static final int HYSTERESIS_DB_DISABLED = 0; + + /** + * Constructor + * + * @param signalMeasurement Signal Measurement Type + * @param hysteresisMs hysteresisMs + * @param hysteresisDb hysteresisDb + * @param thresholds threshold value + * @param isEnabled isEnabled + */ + public SignalThresholdInfo(@SignalMeasurementType int signalMeasurement, + int hysteresisMs, int hysteresisDb, @NonNull int [] thresholds, boolean isEnabled) { + mSignalMeasurement = signalMeasurement; + mHysteresisMs = hysteresisMs < 0 ? HYSTERESIS_MS_DISABLED : hysteresisMs; + mHysteresisDb = hysteresisDb < 0 ? HYSTERESIS_DB_DISABLED : hysteresisDb; + mThresholds = thresholds == null ? null : thresholds.clone(); + mIsEnabled = isEnabled; + } + + public @SignalMeasurementType int getSignalMeasurement() { + return mSignalMeasurement; + } + + public int getHysteresisMs() { + return mHysteresisMs; + } + + public int getHysteresisDb() { + return mHysteresisDb; + } + + public boolean isEnabled() { + return mIsEnabled; + } + + public int[] getThresholds() { + return mThresholds == null ? null : mThresholds.clone(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mSignalMeasurement); + out.writeInt(mHysteresisMs); + out.writeInt(mHysteresisDb); + out.writeIntArray(mThresholds); + out.writeBoolean(mIsEnabled); + } + + private SignalThresholdInfo(Parcel in) { + mSignalMeasurement = in.readInt(); + mHysteresisMs = in.readInt(); + mHysteresisDb = in.readInt(); + mThresholds = in.createIntArray(); + mIsEnabled = in.readBoolean(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (!(o instanceof SignalThresholdInfo)) { + return false; + } + + SignalThresholdInfo other = (SignalThresholdInfo) o; + return mSignalMeasurement == other.mSignalMeasurement + && mHysteresisMs == other.mHysteresisMs + && mHysteresisDb == other.mHysteresisDb + && Arrays.equals(mThresholds, other.mThresholds) + && mIsEnabled == other.mIsEnabled; + } + + @Override + public int hashCode() { + return Objects.hash( + mSignalMeasurement, mHysteresisMs, mHysteresisDb, mThresholds, mIsEnabled); + } + + public static final @NonNull Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public SignalThresholdInfo createFromParcel(Parcel in) { + return new SignalThresholdInfo(in); + } + + @Override + public SignalThresholdInfo[] newArray(int size) { + return new SignalThresholdInfo[size]; + } + }; + + @Override + public String toString() { + return new StringBuilder("SignalThresholdInfo{") + .append("mSignalMeasurement=").append(mSignalMeasurement) + .append("mHysteresisMs=").append(mSignalMeasurement) + .append("mHysteresisDb=").append(mHysteresisDb) + .append("mThresholds=").append(Arrays.toString(mThresholds)) + .append("mIsEnabled=").append(mIsEnabled) + .append("}").toString(); + } +}