1388 lines
59 KiB
Java
1388 lines
59 KiB
Java
/*
|
|
* Copyright (C) 2021 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.internal.telephony;
|
|
|
|
import static android.telephony.TelephonyManager.HAL_SERVICE_NETWORK;
|
|
|
|
import android.annotation.NonNull;
|
|
import android.annotation.Nullable;
|
|
import android.content.Context;
|
|
import android.os.AsyncResult;
|
|
import android.os.Handler;
|
|
import android.os.IBinder;
|
|
import android.os.Message;
|
|
import android.os.PersistableBundle;
|
|
import android.os.Registrant;
|
|
import android.os.RegistrantList;
|
|
import android.os.RemoteException;
|
|
import android.telephony.AccessNetworkConstants;
|
|
import android.telephony.AnomalyReporter;
|
|
import android.telephony.CarrierConfigManager;
|
|
import android.telephony.CellIdentity;
|
|
import android.telephony.CellIdentityLte;
|
|
import android.telephony.CellIdentityNr;
|
|
import android.telephony.CellInfo;
|
|
import android.telephony.CellSignalStrengthLte;
|
|
import android.telephony.CellSignalStrengthNr;
|
|
import android.telephony.ServiceState;
|
|
import android.telephony.SignalStrength;
|
|
import android.telephony.SignalStrengthUpdateRequest;
|
|
import android.telephony.SignalThresholdInfo;
|
|
import android.telephony.SubscriptionInfo;
|
|
import android.telephony.TelephonyManager;
|
|
import android.util.LocalLog;
|
|
import android.util.Pair;
|
|
|
|
import com.android.internal.annotations.VisibleForTesting;
|
|
import com.android.internal.telephony.subscription.SubscriptionManagerService;
|
|
import com.android.internal.telephony.util.ArrayUtils;
|
|
import com.android.internal.telephony.util.TelephonyUtils;
|
|
import com.android.internal.util.IndentingPrintWriter;
|
|
import com.android.telephony.Rlog;
|
|
|
|
import java.io.FileDescriptor;
|
|
import java.io.PrintWriter;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.NoSuchElementException;
|
|
import java.util.Objects;
|
|
import java.util.Set;
|
|
import java.util.TreeSet;
|
|
import java.util.UUID;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
import java.util.regex.PatternSyntaxException;
|
|
|
|
/**
|
|
* SignalStrengthController handles signal polling request and unsolicited signal strength update.
|
|
*/
|
|
public class SignalStrengthController extends Handler {
|
|
private static final boolean DBG = false; /* STOPSHIP if true */
|
|
private static final String TAG = "SSCtr";
|
|
|
|
private static final long SIGNAL_STRENGTH_REFRESH_THRESHOLD_IN_MS =
|
|
TimeUnit.SECONDS.toMillis(10);
|
|
/** Signal strength poll rate. */
|
|
private static final long POLL_PERIOD_MILLIS = TimeUnit.SECONDS.toMillis(20);
|
|
private static final int INVALID_ARFCN = -1;
|
|
|
|
/** Required magnitude change between unsolicited SignalStrength reports. */
|
|
private static final int REPORTING_HYSTERESIS_DB = 2;
|
|
/** Minimum time between unsolicited SignalStrength reports. */
|
|
private static final int REPORTING_HYSTERESIS_MILLIS = 3000;
|
|
/**
|
|
* A threshold within which (inclusive) the application requested signal strength
|
|
* thresholds will be aligned with threholds set in advance (by system or other apps).
|
|
* Since the alignment applies to both directions, the value is set to halt of
|
|
* REPORTING_HYSTERESIS_DB to respect it while without introducing additional gaps for
|
|
* thresholds set by apps.
|
|
*/
|
|
private static final int ALIGNMENT_HYSTERESIS_DB = 1;
|
|
|
|
private static final int EVENT_SET_SIGNAL_STRENGTH_UPDATE_REQUEST = 1;
|
|
private static final int EVENT_CLEAR_SIGNAL_STRENGTH_UPDATE_REQUEST = 2;
|
|
private static final int EVENT_ON_DEVICE_IDLE_STATE_CHANGED = 3;
|
|
private static final int EVENT_RIL_CONNECTED = 4;
|
|
private static final int EVENT_RADIO_AVAILABLE = 5;
|
|
private static final int EVENT_GET_SIGNAL_STRENGTH = 6;
|
|
private static final int EVENT_POLL_SIGNAL_STRENGTH = 7;
|
|
private static final int EVENT_SIGNAL_STRENGTH_UPDATE = 8;
|
|
public static final int EVENT_POLL_SIGNAL_STRENGTH_DONE = 9;
|
|
private static final int EVENT_SERVICE_STATE_CHANGED = 10;
|
|
|
|
@NonNull
|
|
private final Phone mPhone;
|
|
@NonNull
|
|
private final CommandsInterface mCi;
|
|
@NonNull
|
|
private final RegistrantList mSignalStrengthChangedRegistrants = new RegistrantList();
|
|
@NonNull
|
|
private SignalStrength mSignalStrength;
|
|
private long mSignalStrengthUpdatedTime;
|
|
@Nullable
|
|
private SignalStrength mLastSignalStrength = null;
|
|
|
|
/**
|
|
* List of LTE EARFCNs (E-UTRAN Absolute Radio Frequency Channel Number,
|
|
* Reference: 3GPP TS 36.104 5.4.3)
|
|
* inclusive ranges for which the lte rsrp boost is applied
|
|
*/
|
|
@Nullable
|
|
private ArrayList<Pair<Integer, Integer>> mEarfcnPairListForRsrpBoost = null;
|
|
/**
|
|
* Offset which is reduced from the rsrp threshold while calculating signal strength level.
|
|
*/
|
|
private int mLteRsrpBoost = 0;
|
|
/**
|
|
* Ranges of NR ARFCNs (5G Absolute Radio Frequency Channel Number,
|
|
* Reference: 3GPP TS 38.104)
|
|
* inclusive ranges for which the corresponding nr rsrp boost is applied
|
|
*/
|
|
@Nullable
|
|
private ArrayList<Pair<Integer, Integer>> mNrarfcnRangeListForRsrpBoost = null;
|
|
@Nullable
|
|
private int[] mNrRsrpBoost = null;
|
|
@NonNull
|
|
private final Object mRsrpBoostLock = new Object();
|
|
|
|
@NonNull
|
|
private final List<SignalRequestRecord> mSignalRequestRecords = new ArrayList<>();
|
|
|
|
@NonNull
|
|
private PersistableBundle mCarrierConfig;
|
|
|
|
@NonNull
|
|
private final LocalLog mLocalLog = new LocalLog(64);
|
|
|
|
private final AtomicBoolean mNTNConnected = new AtomicBoolean(false);
|
|
|
|
public SignalStrengthController(@NonNull Phone phone) {
|
|
mPhone = phone;
|
|
mCi = mPhone.mCi;
|
|
|
|
mCi.registerForRilConnected(this, EVENT_RIL_CONNECTED, null);
|
|
mCi.registerForAvailable(this, EVENT_RADIO_AVAILABLE, null);
|
|
mCi.setOnSignalStrengthUpdate(this, EVENT_SIGNAL_STRENGTH_UPDATE, null);
|
|
setSignalStrengthDefaultValues();
|
|
|
|
CarrierConfigManager ccm = mPhone.getContext().getSystemService(CarrierConfigManager.class);
|
|
mCarrierConfig = getCarrierConfig();
|
|
// Callback which directly handle config change should be executed on handler thread
|
|
ccm.registerCarrierConfigChangeListener(this::post,
|
|
(slotIndex, subId, carrierId, specificCarrierId) ->
|
|
onCarrierConfigurationChanged(slotIndex));
|
|
|
|
mPhone.registerForServiceStateChanged(this, EVENT_SERVICE_STATE_CHANGED, null);
|
|
}
|
|
|
|
@Override
|
|
public void handleMessage(Message msg) {
|
|
if (DBG) log("received event " + msg.what);
|
|
AsyncResult ar;
|
|
|
|
switch (msg.what) {
|
|
case EVENT_RIL_CONNECTED: // fall through
|
|
case EVENT_RADIO_AVAILABLE:
|
|
onReset();
|
|
break;
|
|
case EVENT_SET_SIGNAL_STRENGTH_UPDATE_REQUEST: {
|
|
Pair<SignalRequestRecord, Message> pair =
|
|
(Pair<SignalRequestRecord, Message>) msg.obj;
|
|
SignalRequestRecord record = pair.first;
|
|
Message onCompleted = pair.second;
|
|
AsyncResult ret = AsyncResult.forMessage(onCompleted);
|
|
|
|
// TODO(b/177956310): Check subId to filter out old request until a better solution
|
|
boolean dupRequest = mSignalRequestRecords.stream().anyMatch(
|
|
srr -> srr.mCallingUid == record.mCallingUid
|
|
&& srr.mSubId == record.mSubId);
|
|
if (dupRequest) {
|
|
ret.exception = new IllegalStateException(
|
|
"setSignalStrengthUpdateRequest called again with same subId");
|
|
onCompleted.sendToTarget();
|
|
break;
|
|
}
|
|
|
|
try {
|
|
record.mRequest.getLiveToken().linkToDeath(record, 0);
|
|
} catch (RemoteException | NullPointerException ex) {
|
|
ret.exception = new IllegalStateException(
|
|
"Signal request client is already dead.");
|
|
onCompleted.sendToTarget();
|
|
break;
|
|
}
|
|
|
|
mSignalRequestRecords.add(record);
|
|
|
|
updateAlwaysReportSignalStrength();
|
|
updateReportingCriteria();
|
|
|
|
onCompleted.sendToTarget();
|
|
break;
|
|
}
|
|
|
|
case EVENT_CLEAR_SIGNAL_STRENGTH_UPDATE_REQUEST: {
|
|
Pair<SignalRequestRecord, Message> pair =
|
|
(Pair<SignalRequestRecord, Message>) msg.obj;
|
|
SignalRequestRecord record = pair.first;
|
|
Message onCompleted = pair.second;
|
|
|
|
// for loop with removal may cause ConcurrentModificationException
|
|
Iterator<SignalRequestRecord> it = mSignalRequestRecords.iterator();
|
|
while (it.hasNext()) {
|
|
SignalRequestRecord srr = it.next();
|
|
if (srr.mRequest.getLiveToken().equals(record.mRequest.getLiveToken())) {
|
|
try {
|
|
srr.mRequest.getLiveToken().unlinkToDeath(srr, 0);
|
|
} catch (NoSuchElementException ignored) {
|
|
// Either never linked or has already unlinked, ignore anyway
|
|
}
|
|
it.remove();
|
|
}
|
|
}
|
|
|
|
updateAlwaysReportSignalStrength();
|
|
updateReportingCriteria();
|
|
|
|
if (onCompleted != null) {
|
|
AsyncResult ret = AsyncResult.forMessage(onCompleted);
|
|
onCompleted.sendToTarget();
|
|
}
|
|
break;
|
|
}
|
|
|
|
case EVENT_ON_DEVICE_IDLE_STATE_CHANGED: {
|
|
updateReportingCriteria();
|
|
break;
|
|
}
|
|
|
|
case EVENT_POLL_SIGNAL_STRENGTH_DONE: // fall through
|
|
case EVENT_GET_SIGNAL_STRENGTH: {
|
|
// This callback is called when signal strength is polled
|
|
// all by itself
|
|
|
|
if (!(mCi.getRadioState() == TelephonyManager.RADIO_POWER_ON)) {
|
|
// Polling will continue when radio turns back on
|
|
return;
|
|
}
|
|
ar = (AsyncResult) msg.obj;
|
|
onSignalStrengthResult(ar);
|
|
break;
|
|
}
|
|
|
|
case EVENT_POLL_SIGNAL_STRENGTH: {
|
|
// Just poll signal strength...not part of pollState()
|
|
|
|
mCi.getSignalStrength(obtainMessage(EVENT_POLL_SIGNAL_STRENGTH_DONE));
|
|
break;
|
|
}
|
|
|
|
case EVENT_SIGNAL_STRENGTH_UPDATE: {
|
|
// This is a notification from CommandsInterface.setOnSignalStrengthUpdate
|
|
|
|
ar = (AsyncResult) msg.obj;
|
|
onSignalStrengthResult(ar);
|
|
break;
|
|
}
|
|
|
|
case EVENT_SERVICE_STATE_CHANGED: {
|
|
onServiceStateChanged((ServiceState) ((AsyncResult) msg.obj).result);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
log("Unhandled message with number: " + msg.what);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void dispose() {
|
|
mCi.unSetOnSignalStrengthUpdate(this);
|
|
}
|
|
|
|
/**
|
|
* Called when RIL is connected during boot up or after modem restart. Set the default criteria
|
|
* so that modem can start with default state before updated criteria is ready.
|
|
*/
|
|
private void onReset() {
|
|
setDefaultSignalStrengthReportingCriteria();
|
|
}
|
|
|
|
void getSignalStrengthFromCi() {
|
|
mCi.getSignalStrength(obtainMessage(EVENT_GET_SIGNAL_STRENGTH));
|
|
}
|
|
|
|
/**
|
|
* Send signal-strength-changed notification if changed. Called for both solicited and
|
|
* unsolicited signal strength updates.
|
|
*/
|
|
private void onSignalStrengthResult(@NonNull AsyncResult ar) {
|
|
// This signal is used for both voice and data radio signal so parse all fields.
|
|
|
|
SignalStrength signalStrength;
|
|
if ((ar.exception == null) && (ar.result != null)) {
|
|
signalStrength = (SignalStrength) ar.result;
|
|
} else {
|
|
loge("onSignalStrengthResult() Exception from RIL : " + ar.exception);
|
|
signalStrength = new SignalStrength();
|
|
}
|
|
updateSignalStrength(signalStrength);
|
|
}
|
|
|
|
/**
|
|
* Set {@code mSignalStrength} to the input argument {@code signalStrength}, update its level,
|
|
* and send signal-strength-changed notification if changed.
|
|
*
|
|
* @param signalStrength The new SignalStrength used for updating {@code mSignalStrength}.
|
|
*/
|
|
private void updateSignalStrength(@NonNull SignalStrength signalStrength) {
|
|
mSignalStrength = maybeOverrideSignalStrengthForTest(signalStrength);
|
|
ServiceStateTracker serviceStateTracker = mPhone.getServiceStateTracker();
|
|
if (serviceStateTracker != null) {
|
|
mSignalStrength.updateLevel(mCarrierConfig, serviceStateTracker.mSS);
|
|
} else {
|
|
loge("updateSignalStrength: serviceStateTracker is null");
|
|
}
|
|
mSignalStrengthUpdatedTime = System.currentTimeMillis();
|
|
notifySignalStrength();
|
|
}
|
|
|
|
/**
|
|
* For debug test build, override signal strength for testing.
|
|
* @param original The real signal strength to use if not in testing mode.
|
|
* @return The signal strength to broadcast to the external.
|
|
*/
|
|
@NonNull private SignalStrength maybeOverrideSignalStrengthForTest(
|
|
@NonNull SignalStrength original) {
|
|
return TelephonyUtils.IS_DEBUGGABLE && mPhone.getTelephonyTester() != null
|
|
? Objects.requireNonNullElse(mPhone.getTelephonyTester()
|
|
.getOverriddenSignalStrength(), original) : original;
|
|
}
|
|
|
|
/**
|
|
* @return signal strength
|
|
*/
|
|
@NonNull
|
|
public SignalStrength getSignalStrength() {
|
|
if (shouldRefreshSignalStrength()) {
|
|
log("getSignalStrength() refreshing signal strength.");
|
|
obtainMessage(EVENT_POLL_SIGNAL_STRENGTH).sendToTarget();
|
|
}
|
|
return mSignalStrength;
|
|
}
|
|
|
|
private boolean shouldRefreshSignalStrength() {
|
|
long curTime = System.currentTimeMillis();
|
|
|
|
// If last signal strength is older than 10 seconds, or somehow if curTime is smaller
|
|
// than mSignalStrengthUpdatedTime (system time update), it's considered stale.
|
|
boolean isStale = (mSignalStrengthUpdatedTime > curTime)
|
|
|| (curTime - mSignalStrengthUpdatedTime > SIGNAL_STRENGTH_REFRESH_THRESHOLD_IN_MS);
|
|
if (!isStale) return false;
|
|
|
|
List<SubscriptionInfo> subInfoList = SubscriptionManagerService.getInstance()
|
|
.getActiveSubscriptionInfoList(mPhone.getContext().getOpPackageName(),
|
|
mPhone.getContext().getAttributionTag(), true/*isForAllProfile*/);
|
|
|
|
if (!ArrayUtils.isEmpty(subInfoList)) {
|
|
for (SubscriptionInfo info : subInfoList) {
|
|
// If we have an active opportunistic subscription whose data is IN_SERVICE,
|
|
// we need to get signal strength to decide data switching threshold. In this case,
|
|
// we poll latest signal strength from modem.
|
|
if (info.isOpportunistic()) {
|
|
TelephonyManager tm = TelephonyManager.from(mPhone.getContext())
|
|
.createForSubscriptionId(info.getSubscriptionId());
|
|
ServiceState ss = tm.getServiceState();
|
|
if (ss != null
|
|
&& ss.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Update signal strength reporting criteria from the carrier config
|
|
*/
|
|
@VisibleForTesting
|
|
public void updateReportingCriteria() {
|
|
List<SignalThresholdInfo> signalThresholdInfos = new ArrayList<>();
|
|
|
|
int[] gsmRssiThresholds = mCarrierConfig.getIntArray(
|
|
CarrierConfigManager.KEY_GSM_RSSI_THRESHOLDS_INT_ARRAY);
|
|
if (gsmRssiThresholds != null) {
|
|
signalThresholdInfos.add(
|
|
validateAndCreateSignalThresholdInfo(
|
|
SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI,
|
|
gsmRssiThresholds,
|
|
AccessNetworkThresholds.GERAN,
|
|
AccessNetworkConstants.AccessNetworkType.GERAN,
|
|
true));
|
|
}
|
|
|
|
int[] wcdmaRscpThresholds = mCarrierConfig.getIntArray(
|
|
CarrierConfigManager.KEY_WCDMA_RSCP_THRESHOLDS_INT_ARRAY);
|
|
if (wcdmaRscpThresholds != null) {
|
|
signalThresholdInfos.add(
|
|
validateAndCreateSignalThresholdInfo(
|
|
SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP,
|
|
wcdmaRscpThresholds,
|
|
AccessNetworkThresholds.UTRAN,
|
|
AccessNetworkConstants.AccessNetworkType.UTRAN,
|
|
true));
|
|
}
|
|
|
|
int lteMeasurementEnabled = mCarrierConfig.getInt(isUsingNonTerrestrialNetwork()
|
|
? CarrierConfigManager.KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT
|
|
: CarrierConfigManager.KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT,
|
|
CellSignalStrengthLte.USE_RSRP);
|
|
int[] lteRsrpThresholds = mCarrierConfig.getIntArray(isUsingNonTerrestrialNetwork()
|
|
? CarrierConfigManager.KEY_NTN_LTE_RSRP_THRESHOLDS_INT_ARRAY
|
|
: CarrierConfigManager.KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY);
|
|
if (lteRsrpThresholds != null) {
|
|
signalThresholdInfos.add(
|
|
validateAndCreateSignalThresholdInfo(
|
|
SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP,
|
|
lteRsrpThresholds,
|
|
AccessNetworkThresholds.EUTRAN_RSRP,
|
|
AccessNetworkConstants.AccessNetworkType.EUTRAN,
|
|
(lteMeasurementEnabled & CellSignalStrengthLte.USE_RSRP) != 0));
|
|
}
|
|
|
|
if (mPhone.getHalVersion(HAL_SERVICE_NETWORK).greaterOrEqual(RIL.RADIO_HAL_VERSION_1_5)) {
|
|
int[] lteRsrqThresholds = mCarrierConfig.getIntArray(isUsingNonTerrestrialNetwork()
|
|
? CarrierConfigManager.KEY_NTN_LTE_RSRQ_THRESHOLDS_INT_ARRAY :
|
|
CarrierConfigManager.KEY_LTE_RSRQ_THRESHOLDS_INT_ARRAY);
|
|
if (lteRsrqThresholds != null) {
|
|
signalThresholdInfos.add(
|
|
validateAndCreateSignalThresholdInfo(
|
|
SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ,
|
|
lteRsrqThresholds,
|
|
AccessNetworkThresholds.EUTRAN_RSRQ,
|
|
AccessNetworkConstants.AccessNetworkType.EUTRAN,
|
|
(lteMeasurementEnabled & CellSignalStrengthLte.USE_RSRQ) != 0));
|
|
}
|
|
|
|
int[] lteRssnrThresholds = mCarrierConfig.getIntArray(isUsingNonTerrestrialNetwork()
|
|
? CarrierConfigManager.KEY_NTN_LTE_RSSNR_THRESHOLDS_INT_ARRAY :
|
|
CarrierConfigManager.KEY_LTE_RSSNR_THRESHOLDS_INT_ARRAY);
|
|
if (lteRssnrThresholds != null) {
|
|
signalThresholdInfos.add(
|
|
validateAndCreateSignalThresholdInfo(
|
|
SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR,
|
|
lteRssnrThresholds,
|
|
AccessNetworkThresholds.EUTRAN_RSSNR,
|
|
AccessNetworkConstants.AccessNetworkType.EUTRAN,
|
|
(lteMeasurementEnabled & CellSignalStrengthLte.USE_RSSNR) != 0));
|
|
}
|
|
|
|
int nrMeasurementEnabled = mCarrierConfig.getInt(CarrierConfigManager
|
|
.KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT, CellSignalStrengthNr.USE_SSRSRP);
|
|
int[] nrSsrsrpThresholds = mCarrierConfig.getIntArray(
|
|
CarrierConfigManager.KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY);
|
|
if (nrSsrsrpThresholds != null) {
|
|
signalThresholdInfos.add(
|
|
validateAndCreateSignalThresholdInfo(
|
|
SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP,
|
|
nrSsrsrpThresholds,
|
|
AccessNetworkThresholds.NGRAN_SSRSRP,
|
|
AccessNetworkConstants.AccessNetworkType.NGRAN,
|
|
(nrMeasurementEnabled & CellSignalStrengthNr.USE_SSRSRP) != 0));
|
|
}
|
|
|
|
int[] nrSsrsrqThresholds = mCarrierConfig.getIntArray(
|
|
CarrierConfigManager.KEY_5G_NR_SSRSRQ_THRESHOLDS_INT_ARRAY);
|
|
if (nrSsrsrqThresholds != null) {
|
|
signalThresholdInfos.add(
|
|
validateAndCreateSignalThresholdInfo(
|
|
SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ,
|
|
nrSsrsrqThresholds,
|
|
AccessNetworkThresholds.NGRAN_SSRSRQ,
|
|
AccessNetworkConstants.AccessNetworkType.NGRAN,
|
|
(nrMeasurementEnabled & CellSignalStrengthNr.USE_SSRSRQ) != 0));
|
|
}
|
|
|
|
int[] nrSssinrThresholds = mCarrierConfig.getIntArray(
|
|
CarrierConfigManager.KEY_5G_NR_SSSINR_THRESHOLDS_INT_ARRAY);
|
|
if (nrSssinrThresholds != null) {
|
|
signalThresholdInfos.add(
|
|
validateAndCreateSignalThresholdInfo(
|
|
SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR,
|
|
nrSssinrThresholds,
|
|
AccessNetworkThresholds.NGRAN_SSSINR,
|
|
AccessNetworkConstants.AccessNetworkType.NGRAN,
|
|
(nrMeasurementEnabled & CellSignalStrengthNr.USE_SSSINR) != 0));
|
|
}
|
|
|
|
int[] wcdmaEcnoThresholds = mCarrierConfig.getIntArray(
|
|
CarrierConfigManager.KEY_WCDMA_ECNO_THRESHOLDS_INT_ARRAY);
|
|
if (wcdmaEcnoThresholds != null) {
|
|
signalThresholdInfos.add(
|
|
validateAndCreateSignalThresholdInfo(
|
|
SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_ECNO,
|
|
wcdmaEcnoThresholds,
|
|
AccessNetworkThresholds.UTRAN_ECNO,
|
|
AccessNetworkConstants.AccessNetworkType.UTRAN,
|
|
false));
|
|
}
|
|
|
|
}
|
|
|
|
consolidatedAndSetReportingCriteria(signalThresholdInfos);
|
|
}
|
|
|
|
private void setDefaultSignalStrengthReportingCriteria() {
|
|
List<SignalThresholdInfo> signalThresholdInfos = new ArrayList<>();
|
|
|
|
signalThresholdInfos.add(
|
|
createSignalThresholdsInfo(
|
|
SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI,
|
|
AccessNetworkThresholds.GERAN,
|
|
AccessNetworkConstants.AccessNetworkType.GERAN,
|
|
true));
|
|
signalThresholdInfos.add(
|
|
createSignalThresholdsInfo(
|
|
SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP,
|
|
AccessNetworkThresholds.UTRAN,
|
|
AccessNetworkConstants.AccessNetworkType.UTRAN,
|
|
true));
|
|
signalThresholdInfos.add(
|
|
createSignalThresholdsInfo(
|
|
SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP,
|
|
AccessNetworkThresholds.EUTRAN_RSRP,
|
|
AccessNetworkConstants.AccessNetworkType.EUTRAN,
|
|
true));
|
|
signalThresholdInfos.add(
|
|
createSignalThresholdsInfo(
|
|
SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI,
|
|
AccessNetworkThresholds.CDMA2000,
|
|
AccessNetworkConstants.AccessNetworkType.CDMA2000,
|
|
true));
|
|
|
|
if (mPhone.getHalVersion(HAL_SERVICE_NETWORK).greaterOrEqual(RIL.RADIO_HAL_VERSION_1_5)) {
|
|
signalThresholdInfos.add(
|
|
createSignalThresholdsInfo(
|
|
SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ,
|
|
AccessNetworkThresholds.EUTRAN_RSRQ,
|
|
AccessNetworkConstants.AccessNetworkType.EUTRAN,
|
|
false));
|
|
signalThresholdInfos.add(
|
|
createSignalThresholdsInfo(
|
|
SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR,
|
|
AccessNetworkThresholds.EUTRAN_RSSNR,
|
|
AccessNetworkConstants.AccessNetworkType.EUTRAN,
|
|
true));
|
|
|
|
// Defaultly we only need SSRSRP for NGRAN signal criteria reporting
|
|
signalThresholdInfos.add(
|
|
createSignalThresholdsInfo(
|
|
SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP,
|
|
AccessNetworkThresholds.NGRAN_SSRSRP,
|
|
AccessNetworkConstants.AccessNetworkType.NGRAN,
|
|
true));
|
|
signalThresholdInfos.add(
|
|
createSignalThresholdsInfo(
|
|
SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ,
|
|
AccessNetworkThresholds.NGRAN_SSRSRQ,
|
|
AccessNetworkConstants.AccessNetworkType.NGRAN,
|
|
false));
|
|
signalThresholdInfos.add(
|
|
createSignalThresholdsInfo(
|
|
SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR,
|
|
AccessNetworkThresholds.NGRAN_SSSINR,
|
|
AccessNetworkConstants.AccessNetworkType.NGRAN,
|
|
false));
|
|
signalThresholdInfos.add(
|
|
createSignalThresholdsInfo(
|
|
SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_ECNO,
|
|
AccessNetworkThresholds.UTRAN_ECNO,
|
|
AccessNetworkConstants.AccessNetworkType.UTRAN,
|
|
false));
|
|
}
|
|
|
|
consolidatedAndSetReportingCriteria(signalThresholdInfos);
|
|
}
|
|
|
|
private void consolidatedAndSetReportingCriteria(
|
|
@NonNull List<SignalThresholdInfo> signalThresholdInfos) {
|
|
List<SignalThresholdInfo> consolidatedSignalThresholdInfos = new ArrayList<>(
|
|
signalThresholdInfos.size());
|
|
for (SignalThresholdInfo signalThresholdInfo : signalThresholdInfos) {
|
|
final int ran = signalThresholdInfo.getRadioAccessNetworkType();
|
|
final int measurementType = signalThresholdInfo.getSignalMeasurementType();
|
|
final boolean isEnabledForSystem =
|
|
signalThresholdInfo.isEnabled() && shouldHonorSystemThresholds();
|
|
int[] consolidatedThresholds =
|
|
getConsolidatedSignalThresholds(
|
|
ran,
|
|
measurementType,
|
|
isEnabledForSystem
|
|
? signalThresholdInfo.getThresholds()
|
|
: new int[]{},
|
|
ALIGNMENT_HYSTERESIS_DB);
|
|
boolean isEnabledForAppRequest =
|
|
shouldEnableSignalThresholdForAppRequest(
|
|
ran,
|
|
measurementType,
|
|
mPhone.getSubId(),
|
|
mPhone.isDeviceIdle());
|
|
int hysteresisDb = getMinimumHysteresisDb(isEnabledForAppRequest, ran, measurementType,
|
|
consolidatedThresholds);
|
|
consolidatedSignalThresholdInfos.add(
|
|
new SignalThresholdInfo.Builder()
|
|
.setRadioAccessNetworkType(ran)
|
|
.setSignalMeasurementType(measurementType)
|
|
.setHysteresisMs(REPORTING_HYSTERESIS_MILLIS)
|
|
.setHysteresisDb(hysteresisDb)
|
|
.setThresholds(consolidatedThresholds, true /*isSystem*/)
|
|
.setIsEnabled(isEnabledForSystem || isEnabledForAppRequest)
|
|
.build());
|
|
}
|
|
mCi.setSignalStrengthReportingCriteria(consolidatedSignalThresholdInfos, null);
|
|
|
|
localLog("setSignalStrengthReportingCriteria consolidatedSignalThresholdInfos="
|
|
+ consolidatedSignalThresholdInfos);
|
|
}
|
|
|
|
/**
|
|
* Return the minimum hysteresis dB from all available sources:
|
|
* - system default
|
|
* - value set by client through API
|
|
* - threshold delta
|
|
*/
|
|
@VisibleForTesting
|
|
public int getMinimumHysteresisDb(boolean isEnabledForAppRequest, int ran, int measurementType,
|
|
final int[] consolidatedThresholdList) {
|
|
|
|
int currHysteresisDb = getHysteresisDbFromCarrierConfig(ran, measurementType);
|
|
|
|
if (isEnabledForAppRequest) {
|
|
// Get minimum hysteresisDb at api
|
|
int apiHysteresisDb =
|
|
getHysteresisDbFromSignalThresholdInfoRequests(ran, measurementType);
|
|
|
|
// Choose minimum of hysteresisDb between api Vs current system/cc value set
|
|
currHysteresisDb = Math.min(currHysteresisDb, apiHysteresisDb);
|
|
|
|
// Hal Req: choose hysteresis db value to be smaller of smallest of threshold delta
|
|
currHysteresisDb = computeHysteresisDbOnSmallestThresholdDelta(
|
|
currHysteresisDb, consolidatedThresholdList);
|
|
}
|
|
return currHysteresisDb;
|
|
}
|
|
|
|
/**
|
|
* Get the hysteresis db value from Signal Requests
|
|
* Note: Based on the current use case, there does not exist multile App signal threshold info
|
|
* requests with hysteresis db value, so this logic picks the latest hysteresis db value set.
|
|
*
|
|
* TODO(b/262655157): Support Multiple App Hysteresis DB value customisation
|
|
*/
|
|
private int getHysteresisDbFromSignalThresholdInfoRequests(
|
|
@AccessNetworkConstants.RadioAccessNetworkType int ran,
|
|
@SignalThresholdInfo.SignalMeasurementType int measurement) {
|
|
int apiHysteresisDb = REPORTING_HYSTERESIS_DB;
|
|
for (SignalRequestRecord record : mSignalRequestRecords) {
|
|
for (SignalThresholdInfo info : record.mRequest.getSignalThresholdInfos()) {
|
|
if (isRanAndSignalMeasurementTypeMatch(ran, measurement, info)) {
|
|
if (info.getHysteresisDb() >= 0) {
|
|
apiHysteresisDb = info.getHysteresisDb();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return apiHysteresisDb;
|
|
}
|
|
|
|
private int getHysteresisDbFromCarrierConfig(int ran, int measurement) {
|
|
int configHysteresisDb = REPORTING_HYSTERESIS_DB;
|
|
String configKey = null;
|
|
|
|
switch (ran) {
|
|
case AccessNetworkConstants.AccessNetworkType.GERAN:
|
|
if (measurement == SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI) {
|
|
configKey = CarrierConfigManager.KEY_GERAN_RSSI_HYSTERESIS_DB_INT;
|
|
}
|
|
break;
|
|
case AccessNetworkConstants.AccessNetworkType.UTRAN:
|
|
if (measurement == SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP) {
|
|
configKey = CarrierConfigManager.KEY_UTRAN_RSCP_HYSTERESIS_DB_INT;
|
|
} else if (measurement == SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_ECNO) {
|
|
configKey = CarrierConfigManager.KEY_UTRAN_ECNO_HYSTERESIS_DB_INT;
|
|
}
|
|
break;
|
|
case AccessNetworkConstants.AccessNetworkType.EUTRAN:
|
|
if (measurement == SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP) {
|
|
configKey = CarrierConfigManager.KEY_EUTRAN_RSRP_HYSTERESIS_DB_INT;
|
|
} else if (measurement == SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ) {
|
|
configKey = CarrierConfigManager.KEY_EUTRAN_RSRQ_HYSTERESIS_DB_INT;
|
|
} else if (measurement == SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR) {
|
|
configKey = CarrierConfigManager.KEY_EUTRAN_RSSNR_HYSTERESIS_DB_INT;
|
|
}
|
|
break;
|
|
case AccessNetworkConstants.AccessNetworkType.NGRAN:
|
|
if (measurement == SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP) {
|
|
configKey = CarrierConfigManager.KEY_NGRAN_SSRSRP_HYSTERESIS_DB_INT;
|
|
} else if (measurement == SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ) {
|
|
configKey = CarrierConfigManager.KEY_NGRAN_SSRSRQ_HYSTERESIS_DB_INT;
|
|
} else if (measurement == SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR) {
|
|
configKey = CarrierConfigManager.KEY_NGRAN_SSSINR_HYSTERESIS_DB_INT;
|
|
}
|
|
break;
|
|
default:
|
|
localLog("No matching configuration");
|
|
}
|
|
if (configKey != null) {
|
|
configHysteresisDb = mCarrierConfig.getInt(configKey, REPORTING_HYSTERESIS_DB);
|
|
}
|
|
return configHysteresisDb >= SignalThresholdInfo.HYSTERESIS_DB_MINIMUM
|
|
? configHysteresisDb : REPORTING_HYSTERESIS_DB;
|
|
}
|
|
|
|
/**
|
|
* This method computes the hysteresis db value between smaller of the smallest Threshold Delta
|
|
* and system / cc / api hysteresis db value determined.
|
|
*
|
|
* @param currMinHysteresisDb smaller value between system / cc / api hysteresis db value
|
|
* @param signalThresholdInfoArray consolidated threshold info with App request consolidated.
|
|
* @return current minimum hysteresis db value computed between above params.
|
|
*
|
|
*/
|
|
private int computeHysteresisDbOnSmallestThresholdDelta(
|
|
int currMinHysteresisDb, final int[] signalThresholdInfoArray) {
|
|
int index = 0;
|
|
if (signalThresholdInfoArray.length > 1) {
|
|
while (index != signalThresholdInfoArray.length - 1) {
|
|
if (signalThresholdInfoArray[index + 1] - signalThresholdInfoArray[index]
|
|
< currMinHysteresisDb) {
|
|
currMinHysteresisDb =
|
|
signalThresholdInfoArray[index + 1] - signalThresholdInfoArray[index];
|
|
}
|
|
index++;
|
|
}
|
|
}
|
|
return currMinHysteresisDb;
|
|
}
|
|
|
|
void setSignalStrengthDefaultValues() {
|
|
mSignalStrength = maybeOverrideSignalStrengthForTest(new SignalStrength());
|
|
mSignalStrengthUpdatedTime = System.currentTimeMillis();
|
|
}
|
|
|
|
void notifySignalStrength() {
|
|
if (!mSignalStrength.equals(mLastSignalStrength)) {
|
|
try {
|
|
mSignalStrengthChangedRegistrants.notifyRegistrants();
|
|
mPhone.notifySignalStrength();
|
|
mLastSignalStrength = mSignalStrength;
|
|
} catch (NullPointerException ex) {
|
|
loge("updateSignalStrength() Phone already destroyed: " + ex
|
|
+ "SignalStrength not notified");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Register for SignalStrength changed.
|
|
* @param h Handler to notify
|
|
* @param what msg.what when the message is delivered
|
|
* @param obj msg.obj when the message is delivered
|
|
*/
|
|
public void registerForSignalStrengthChanged(Handler h, int what, Object obj) {
|
|
Registrant r = new Registrant(h, what, obj);
|
|
mSignalStrengthChangedRegistrants.add(r);
|
|
}
|
|
|
|
/**
|
|
* Unregister for SignalStrength changed.
|
|
* @param h Handler to notify
|
|
*/
|
|
public void unregisterForSignalStrengthChanged(Handler h) {
|
|
mSignalStrengthChangedRegistrants.remove(h);
|
|
}
|
|
|
|
/**
|
|
* Print the SignalStrengthController states into the given stream.
|
|
*
|
|
* @param fd The raw file descriptor that the dump is being sent to.
|
|
* @param pw A PrintWriter to which the dump is to be set.
|
|
* @param args Additional arguments to the dump request.
|
|
*/
|
|
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
|
pw.println("SignalStrengthController - phoneId: " + mPhone.getPhoneId());
|
|
pw.println("SignalStrengthController - Log Begin ----");
|
|
mLocalLog.dump(fd, pw, args);
|
|
pw.println("SignalStrengthController - Log End ----");
|
|
|
|
final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
|
|
ipw.increaseIndent();
|
|
pw.println("mSignalRequestRecords=" + mSignalRequestRecords);
|
|
pw.println(" mLastSignalStrength=" + mLastSignalStrength);
|
|
pw.println(" mSignalStrength=" + mSignalStrength);
|
|
pw.println(" mLteRsrpBoost=" + mLteRsrpBoost);
|
|
pw.println(" mNrRsrpBoost=" + Arrays.toString(mNrRsrpBoost));
|
|
pw.println(" mEarfcnPairListForRsrpBoost=" + mEarfcnPairListForRsrpBoost);
|
|
pw.println(" mNrarfcnRangeListForRsrpBoost=" + mNrarfcnRangeListForRsrpBoost);
|
|
ipw.decreaseIndent();
|
|
ipw.flush();
|
|
}
|
|
|
|
/**
|
|
* Set a new request to update the signal strength thresholds.
|
|
*/
|
|
public void setSignalStrengthUpdateRequest(int subId, int callingUid,
|
|
@NonNull SignalStrengthUpdateRequest request, @NonNull Message onCompleted) {
|
|
SignalRequestRecord record = new SignalRequestRecord(subId, callingUid, request);
|
|
sendMessage(obtainMessage(EVENT_SET_SIGNAL_STRENGTH_UPDATE_REQUEST,
|
|
new Pair<SignalRequestRecord, Message>(record, onCompleted)));
|
|
|
|
localLog("setSignalStrengthUpdateRequest"
|
|
+ " subId=" + subId
|
|
+ " callingUid=" + callingUid
|
|
+ " request=" + request);
|
|
}
|
|
|
|
/**
|
|
* Clear the previously set request.
|
|
*/
|
|
public void clearSignalStrengthUpdateRequest(int subId, int callingUid,
|
|
@NonNull SignalStrengthUpdateRequest request, @Nullable Message onCompleted) {
|
|
SignalRequestRecord record = new SignalRequestRecord(subId, callingUid, request);
|
|
sendMessage(obtainMessage(EVENT_CLEAR_SIGNAL_STRENGTH_UPDATE_REQUEST,
|
|
new Pair<SignalRequestRecord, Message>(record, onCompleted)));
|
|
|
|
localLog("clearSignalStrengthUpdateRequest"
|
|
+ " subId=" + subId
|
|
+ " callingUid=" + callingUid
|
|
+ " request=" + request);
|
|
}
|
|
|
|
/**
|
|
* Align all the qualified thresholds set from applications to the {@code systemThresholds}
|
|
* and consolidate a new thresholds array, follow rules below:
|
|
* 1. All threshold values (whose interval is guaranteed to be larger than hysteresis) in
|
|
* {@code systemThresholds} will keep as it.
|
|
* 2. Any threshold from apps that has interval less than hysteresis from any threshold in
|
|
* {@code systemThresholds} will be removed.
|
|
* 3. The target thresholds will be {@code systemThresholds} + all qualified thresholds from
|
|
* apps, sorted in ascending order.
|
|
*/
|
|
@VisibleForTesting
|
|
@NonNull
|
|
public int[] getConsolidatedSignalThresholds(int ran, int measurement,
|
|
@Nullable int[] systemThresholds, int hysteresis) {
|
|
|
|
// TreeSet with comparator that will filter element with interval less than hysteresis
|
|
// from any current element
|
|
Set<Integer> target = new TreeSet<>((x, y) -> {
|
|
if (y >= x - hysteresis && y <= x + hysteresis) {
|
|
return 0;
|
|
}
|
|
return Integer.compare(x, y);
|
|
});
|
|
|
|
if (systemThresholds != null) {
|
|
for (int systemThreshold : systemThresholds) {
|
|
target.add(systemThreshold);
|
|
}
|
|
}
|
|
|
|
final boolean isDeviceIdle = mPhone.isDeviceIdle();
|
|
final int curSubId = mPhone.getSubId();
|
|
// The total number of record is small (10~15 tops). With each request has at most 5
|
|
// SignalThresholdInfo which has at most 8 thresholds arrays. So the nested loop should
|
|
// not be a concern here.
|
|
for (SignalRequestRecord record : mSignalRequestRecords) {
|
|
if (curSubId != record.mSubId
|
|
|| (isDeviceIdle && !record.mRequest.isReportingRequestedWhileIdle())) {
|
|
continue;
|
|
}
|
|
for (SignalThresholdInfo info : record.mRequest.getSignalThresholdInfos()) {
|
|
if (isRanAndSignalMeasurementTypeMatch(ran, measurement, info)) {
|
|
for (int appThreshold : info.getThresholds()) {
|
|
target.add(appThreshold);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int[] targetArray = new int[target.size()];
|
|
int i = 0;
|
|
for (int element : target) {
|
|
targetArray[i++] = element;
|
|
}
|
|
return targetArray;
|
|
}
|
|
|
|
/**
|
|
* Return true if system thresholds should be honored when consolidating.
|
|
*/
|
|
@VisibleForTesting
|
|
public boolean shouldHonorSystemThresholds() {
|
|
if (!mPhone.isDeviceIdle()) {
|
|
return true;
|
|
}
|
|
|
|
final int curSubId = mPhone.getSubId();
|
|
return mSignalRequestRecords.stream().anyMatch(
|
|
srr -> curSubId == srr.mSubId
|
|
&& srr.mRequest.isSystemThresholdReportingRequestedWhileIdle());
|
|
}
|
|
|
|
/**
|
|
* Get notified when device idle state changed
|
|
*/
|
|
@VisibleForTesting
|
|
public void onDeviceIdleStateChanged(boolean isDeviceIdle) {
|
|
sendMessage(obtainMessage(EVENT_ON_DEVICE_IDLE_STATE_CHANGED, isDeviceIdle));
|
|
|
|
localLog("onDeviceIdleStateChanged isDeviceIdle=" + isDeviceIdle);
|
|
}
|
|
|
|
/**
|
|
* Return true if signal threshold should be enabled due to the apps requests.
|
|
*/
|
|
@VisibleForTesting
|
|
public boolean shouldEnableSignalThresholdForAppRequest(
|
|
@AccessNetworkConstants.RadioAccessNetworkType int ran,
|
|
@SignalThresholdInfo.SignalMeasurementType int measurement,
|
|
int subId,
|
|
boolean isDeviceIdle) {
|
|
for (SignalRequestRecord record : mSignalRequestRecords) {
|
|
if (subId != record.mSubId) {
|
|
continue;
|
|
}
|
|
for (SignalThresholdInfo info : record.mRequest.getSignalThresholdInfos()) {
|
|
if (isRanAndSignalMeasurementTypeMatch(ran, measurement, info)
|
|
&& (!isDeviceIdle || isSignalReportRequestedWhileIdle(record.mRequest))) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private static boolean isRanAndSignalMeasurementTypeMatch(
|
|
@AccessNetworkConstants.RadioAccessNetworkType int ran,
|
|
@SignalThresholdInfo.SignalMeasurementType int measurement,
|
|
@NonNull SignalThresholdInfo info) {
|
|
return ran == info.getRadioAccessNetworkType()
|
|
&& measurement == info.getSignalMeasurementType();
|
|
}
|
|
|
|
private static boolean isSignalReportRequestedWhileIdle(
|
|
@NonNull SignalStrengthUpdateRequest request) {
|
|
return request.isSystemThresholdReportingRequestedWhileIdle()
|
|
|| request.isReportingRequestedWhileIdle();
|
|
}
|
|
|
|
/**
|
|
* Gets the carrier configuration values for a particular subscription.
|
|
*
|
|
* @return A {@link PersistableBundle} containing the config for the given subId,
|
|
* or default values for an invalid subId.
|
|
*/
|
|
@NonNull
|
|
private PersistableBundle getCarrierConfig() {
|
|
CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
|
|
.getSystemService(Context.CARRIER_CONFIG_SERVICE);
|
|
if (configManager != null) {
|
|
// If an invalid subId is used, this bundle will contain default values.
|
|
PersistableBundle config = configManager.getConfigForSubId(mPhone.getSubId());
|
|
if (config != null) {
|
|
return config;
|
|
}
|
|
}
|
|
// Return static default defined in CarrierConfigManager.
|
|
return CarrierConfigManager.getDefaultConfig();
|
|
}
|
|
|
|
private class SignalRequestRecord implements IBinder.DeathRecipient {
|
|
final int mSubId; // subId the request originally applied to
|
|
final int mCallingUid;
|
|
@NonNull
|
|
final SignalStrengthUpdateRequest mRequest;
|
|
|
|
SignalRequestRecord(int subId, int uid, @NonNull SignalStrengthUpdateRequest request) {
|
|
this.mCallingUid = uid;
|
|
this.mSubId = subId;
|
|
this.mRequest = request;
|
|
}
|
|
|
|
@Override
|
|
public void binderDied() {
|
|
localLog("binderDied record=" + this);
|
|
clearSignalStrengthUpdateRequest(mSubId, mCallingUid, mRequest, null /*onCompleted*/);
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
StringBuffer sb = new StringBuffer("SignalRequestRecord {");
|
|
sb.append("mSubId=").append(mSubId);
|
|
sb.append(" mCallingUid=").append(mCallingUid);
|
|
sb.append(" mRequest=").append(mRequest).append("}");
|
|
return sb.toString();
|
|
}
|
|
}
|
|
|
|
private void updateAlwaysReportSignalStrength() {
|
|
final int curSubId = mPhone.getSubId();
|
|
boolean alwaysReport = mSignalRequestRecords.stream().anyMatch(
|
|
srr -> srr.mSubId == curSubId && isSignalReportRequestedWhileIdle(srr.mRequest));
|
|
|
|
// TODO(b/177924721): TM#setAlwaysReportSignalStrength will be removed and we will not
|
|
// worry about unset flag which was set by other client.
|
|
mPhone.setAlwaysReportSignalStrength(alwaysReport);
|
|
}
|
|
|
|
void updateArfcnLists() {
|
|
synchronized (mRsrpBoostLock) {
|
|
mLteRsrpBoost = mCarrierConfig.getInt(
|
|
CarrierConfigManager.KEY_LTE_EARFCNS_RSRP_BOOST_INT, 0);
|
|
String[] earfcnsStringArrayForRsrpBoost = mCarrierConfig.getStringArray(
|
|
CarrierConfigManager.KEY_BOOSTED_LTE_EARFCNS_STRING_ARRAY);
|
|
mEarfcnPairListForRsrpBoost = convertEarfcnStringArrayToPairList(
|
|
earfcnsStringArrayForRsrpBoost);
|
|
|
|
mNrRsrpBoost = mCarrierConfig.getIntArray(
|
|
CarrierConfigManager.KEY_NRARFCNS_RSRP_BOOST_INT_ARRAY);
|
|
String[] nrarfcnsStringArrayForRsrpBoost = mCarrierConfig.getStringArray(
|
|
CarrierConfigManager.KEY_BOOSTED_NRARFCNS_STRING_ARRAY);
|
|
mNrarfcnRangeListForRsrpBoost = convertEarfcnStringArrayToPairList(
|
|
nrarfcnsStringArrayForRsrpBoost);
|
|
|
|
if ((mNrRsrpBoost == null && mNrarfcnRangeListForRsrpBoost != null)
|
|
|| (mNrRsrpBoost != null && mNrarfcnRangeListForRsrpBoost == null)
|
|
|| (mNrRsrpBoost != null && mNrarfcnRangeListForRsrpBoost != null
|
|
&& mNrRsrpBoost.length != mNrarfcnRangeListForRsrpBoost.size())) {
|
|
loge("Invalid parameters for NR RSRP boost");
|
|
mNrRsrpBoost = null;
|
|
mNrarfcnRangeListForRsrpBoost = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
// package private access from ServiceStateTracker
|
|
// TODO(b/219572311): Maintains ArfcnRsrpBoost here only without forwarding by ServiceState
|
|
void updateServiceStateArfcnRsrpBoost(@NonNull ServiceState serviceState,
|
|
@Nullable CellIdentity cellIdentity) {
|
|
if (cellIdentity == null) return;
|
|
|
|
int rsrpBoost = 0;
|
|
int arfcn;
|
|
|
|
synchronized (mRsrpBoostLock) {
|
|
switch (cellIdentity.getType()) {
|
|
case CellInfo.TYPE_LTE:
|
|
arfcn = ((CellIdentityLte) cellIdentity).getEarfcn();
|
|
if (arfcn != INVALID_ARFCN
|
|
&& containsEarfcnInEarfcnRange(mEarfcnPairListForRsrpBoost,
|
|
arfcn) != -1) {
|
|
rsrpBoost = mLteRsrpBoost;
|
|
}
|
|
break;
|
|
case CellInfo.TYPE_NR:
|
|
arfcn = ((CellIdentityNr) cellIdentity).getNrarfcn();
|
|
if (arfcn != INVALID_ARFCN) {
|
|
int index = containsEarfcnInEarfcnRange(mNrarfcnRangeListForRsrpBoost,
|
|
arfcn);
|
|
if (index != -1 && mNrRsrpBoost != null) {
|
|
rsrpBoost = mNrRsrpBoost[index];
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
serviceState.setArfcnRsrpBoost(rsrpBoost);
|
|
}
|
|
|
|
/**
|
|
* Checks if the provided earfcn falls within the range of earfcns.
|
|
*
|
|
* return int index in earfcnPairList if earfcn falls within the provided range; -1 otherwise.
|
|
*/
|
|
private static int containsEarfcnInEarfcnRange(
|
|
@Nullable ArrayList<Pair<Integer, Integer>> earfcnPairList, int earfcn) {
|
|
int index = 0;
|
|
if (earfcnPairList != null) {
|
|
for (Pair<Integer, Integer> earfcnPair : earfcnPairList) {
|
|
if ((earfcn >= earfcnPair.first) && (earfcn <= earfcnPair.second)) {
|
|
return index;
|
|
}
|
|
index++;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Convert the earfcnStringArray to list of pairs.
|
|
*
|
|
* Format of the earfcnsList is expected to be {"erafcn1_start-earfcn1_end",
|
|
* "earfcn2_start-earfcn2_end" ... }
|
|
*/
|
|
@Nullable
|
|
private static ArrayList<Pair<Integer, Integer>> convertEarfcnStringArrayToPairList(
|
|
@Nullable String[] earfcnsList) {
|
|
ArrayList<Pair<Integer, Integer>> earfcnPairList = new ArrayList<Pair<Integer, Integer>>();
|
|
|
|
if (earfcnsList != null) {
|
|
int earfcnStart;
|
|
int earfcnEnd;
|
|
for (int i = 0; i < earfcnsList.length; i++) {
|
|
try {
|
|
String[] earfcns = earfcnsList[i].split("-");
|
|
if (earfcns.length != 2) {
|
|
if (DBG) {
|
|
log("Invalid earfcn range format");
|
|
}
|
|
return null;
|
|
}
|
|
|
|
earfcnStart = Integer.parseInt(earfcns[0]);
|
|
earfcnEnd = Integer.parseInt(earfcns[1]);
|
|
|
|
if (earfcnStart > earfcnEnd) {
|
|
if (DBG) {
|
|
log("Invalid earfcn range format");
|
|
}
|
|
return null;
|
|
}
|
|
|
|
earfcnPairList.add(new Pair<Integer, Integer>(earfcnStart, earfcnEnd));
|
|
} catch (PatternSyntaxException pse) {
|
|
if (DBG) {
|
|
log("Invalid earfcn range format");
|
|
}
|
|
return null;
|
|
} catch (NumberFormatException nfe) {
|
|
if (DBG) {
|
|
log("Invalid earfcn number format");
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
return earfcnPairList;
|
|
}
|
|
|
|
private void onCarrierConfigurationChanged(int slotIndex) {
|
|
if (slotIndex != mPhone.getPhoneId()) return;
|
|
|
|
mCarrierConfig = getCarrierConfig();
|
|
log("Carrier Config changed.");
|
|
|
|
updateArfcnLists();
|
|
updateReportingCriteria();
|
|
updateSignalStrength(new SignalStrength(mSignalStrength));
|
|
}
|
|
|
|
private static SignalThresholdInfo createSignalThresholdsInfo(
|
|
int measurementType, @NonNull int[] thresholds, int ran, boolean isEnabled) {
|
|
return new SignalThresholdInfo.Builder()
|
|
.setSignalMeasurementType(measurementType)
|
|
.setThresholds(thresholds)
|
|
.setRadioAccessNetworkType(ran)
|
|
.setIsEnabled(isEnabled)
|
|
.build();
|
|
}
|
|
|
|
/**
|
|
* Validate the provided signal {@code thresholds} info and fall back to use the
|
|
* {@code defaultThresholds} and report anomaly if invalid to prevent crashing Phone.
|
|
*/
|
|
private static SignalThresholdInfo validateAndCreateSignalThresholdInfo(
|
|
int measurementType, @NonNull int[] thresholds, @NonNull int[] defaultThresholds,
|
|
int ran, boolean isEnabled) {
|
|
SignalThresholdInfo signalThresholdInfo;
|
|
try {
|
|
signalThresholdInfo = new SignalThresholdInfo.Builder()
|
|
.setSignalMeasurementType(measurementType)
|
|
.setThresholds(thresholds)
|
|
.setRadioAccessNetworkType(ran)
|
|
.setIsEnabled(isEnabled)
|
|
.build();
|
|
// TODO(b/295236831): only catch IAE when phone global exception handler is introduced.
|
|
// Although SignalThresholdInfo only throws IAE for invalid carrier configs, we catch
|
|
// all exception to prevent crashing phone before global exception handler is available.
|
|
} catch (Exception e) {
|
|
signalThresholdInfo = new SignalThresholdInfo.Builder()
|
|
.setSignalMeasurementType(measurementType)
|
|
.setThresholds(defaultThresholds)
|
|
.setRadioAccessNetworkType(ran)
|
|
.setIsEnabled(isEnabled)
|
|
.build();
|
|
|
|
AnomalyReporter.reportAnomaly(
|
|
UUID.fromString("28232bc4-78ff-447e-b597-7c054c802407"),
|
|
"Invalid parameter to generate SignalThresholdInfo: "
|
|
+ "measurementType=" + measurementType
|
|
+ ", thresholds=" + Arrays.toString(thresholds)
|
|
+ ", RAN=" + ran
|
|
+ ", isEnabled=" + isEnabled
|
|
+ ". Replaced with default thresholds: " + Arrays.toString(
|
|
defaultThresholds));
|
|
}
|
|
return signalThresholdInfo;
|
|
}
|
|
|
|
/**
|
|
* dBm thresholds that correspond to changes in signal strength indications.
|
|
*/
|
|
private static final class AccessNetworkThresholds {
|
|
|
|
/**
|
|
* List of dBm thresholds for GERAN {@link AccessNetworkConstants.AccessNetworkType}.
|
|
*
|
|
* Calculated from GSM asu level thresholds - TS 27.007 Sec 8.5
|
|
*/
|
|
public static final int[] GERAN = new int[]{
|
|
-109,
|
|
-103,
|
|
-97,
|
|
-89,
|
|
};
|
|
|
|
/**
|
|
* List of default dBm thresholds for UTRAN
|
|
* {@link AccessNetworkConstants.AccessNetworkType}.
|
|
*
|
|
* These thresholds are taken from the WCDMA RSCP defaults in {@link CarrierConfigManager}.
|
|
* See TS 27.007 Sec 8.69.
|
|
*/
|
|
public static final int[] UTRAN = new int[]{
|
|
-114, /* SIGNAL_STRENGTH_POOR */
|
|
-104, /* SIGNAL_STRENGTH_MODERATE */
|
|
-94, /* SIGNAL_STRENGTH_GOOD */
|
|
-84 /* SIGNAL_STRENGTH_GREAT */
|
|
};
|
|
|
|
/**
|
|
* List of default dBm RSRP thresholds for EUTRAN
|
|
* {@link AccessNetworkConstants.AccessNetworkType}.
|
|
*
|
|
* These thresholds are taken from the LTE RSRP defaults in {@link CarrierConfigManager}.
|
|
*/
|
|
public static final int[] EUTRAN_RSRP = new int[]{
|
|
-128, /* SIGNAL_STRENGTH_POOR */
|
|
-118, /* SIGNAL_STRENGTH_MODERATE */
|
|
-108, /* SIGNAL_STRENGTH_GOOD */
|
|
-98, /* SIGNAL_STRENGTH_GREAT */
|
|
};
|
|
|
|
/**
|
|
* List of default dB RSRQ thresholds for EUTRAN
|
|
* {@link AccessNetworkConstants.AccessNetworkType}.
|
|
*
|
|
* These thresholds are taken from the LTE RSRQ defaults in {@link CarrierConfigManager}.
|
|
*/
|
|
public static final int[] EUTRAN_RSRQ = new int[]{
|
|
-20, /* SIGNAL_STRENGTH_POOR */
|
|
-17, /* SIGNAL_STRENGTH_MODERATE */
|
|
-14, /* SIGNAL_STRENGTH_GOOD */
|
|
-11 /* SIGNAL_STRENGTH_GREAT */
|
|
};
|
|
|
|
/**
|
|
* List of default dB RSSNR thresholds for EUTRAN
|
|
* {@link AccessNetworkConstants.AccessNetworkType}.
|
|
*
|
|
* These thresholds are taken from the LTE RSSNR defaults in {@link CarrierConfigManager}.
|
|
*/
|
|
public static final int[] EUTRAN_RSSNR = new int[]{
|
|
-3, /* SIGNAL_STRENGTH_POOR */
|
|
1, /* SIGNAL_STRENGTH_MODERATE */
|
|
5, /* SIGNAL_STRENGTH_GOOD */
|
|
13 /* SIGNAL_STRENGTH_GREAT */
|
|
};
|
|
|
|
/**
|
|
* List of dBm thresholds for CDMA2000 {@link AccessNetworkConstants.AccessNetworkType}.
|
|
*
|
|
* These correspond to EVDO level thresholds.
|
|
*/
|
|
public static final int[] CDMA2000 = new int[]{
|
|
-105,
|
|
-90,
|
|
-75,
|
|
-65
|
|
};
|
|
|
|
/**
|
|
* List of dB thresholds for NGRAN {@link AccessNetworkConstants.AccessNetworkType} SSRSRP
|
|
*/
|
|
public static final int[] NGRAN_SSRSRP = new int[]{
|
|
-110, /* SIGNAL_STRENGTH_POOR */
|
|
-90, /* SIGNAL_STRENGTH_MODERATE */
|
|
-80, /* SIGNAL_STRENGTH_GOOD */
|
|
-65, /* SIGNAL_STRENGTH_GREAT */
|
|
};
|
|
|
|
/**
|
|
* List of dB thresholds for NGRAN {@link AccessNetworkConstants.AccessNetworkType} SSRSRQ
|
|
*/
|
|
public static final int[] NGRAN_SSRSRQ = new int[]{
|
|
-31, /* SIGNAL_STRENGTH_POOR */
|
|
-19, /* SIGNAL_STRENGTH_MODERATE */
|
|
-7, /* SIGNAL_STRENGTH_GOOD */
|
|
6 /* SIGNAL_STRENGTH_GREAT */
|
|
};
|
|
|
|
/**
|
|
* List of dB thresholds for NGRAN {@link AccessNetworkConstants.AccessNetworkType} SSSINR
|
|
*/
|
|
public static final int[] NGRAN_SSSINR = new int[]{
|
|
-5, /* SIGNAL_STRENGTH_POOR */
|
|
5, /* SIGNAL_STRENGTH_MODERATE */
|
|
15, /* SIGNAL_STRENGTH_GOOD */
|
|
30 /* SIGNAL_STRENGTH_GREAT */
|
|
};
|
|
|
|
/**
|
|
* List of dBm thresholds for UTRAN {@link AccessNetworkConstants.AccessNetworkType} ECNO
|
|
*/
|
|
public static final int[] UTRAN_ECNO = new int[]{
|
|
-24, /* SIGNAL_STRENGTH_POOR */
|
|
-14, /* SIGNAL_STRENGTH_MODERATE */
|
|
-6, /* SIGNAL_STRENGTH_GOOD */
|
|
1 /* SIGNAL_STRENGTH_GREAT */
|
|
};
|
|
}
|
|
|
|
private void onServiceStateChanged(ServiceState state) {
|
|
if (state.getState() != ServiceState.STATE_IN_SERVICE) {
|
|
return;
|
|
}
|
|
|
|
if (mNTNConnected.get() != state.isUsingNonTerrestrialNetwork()) {
|
|
log("onServiceStateChanged: update it to " + state.isUsingNonTerrestrialNetwork());
|
|
updateReportingCriteria();
|
|
mNTNConnected.set(state.isUsingNonTerrestrialNetwork());
|
|
}
|
|
}
|
|
|
|
private boolean isUsingNonTerrestrialNetwork() {
|
|
if (mPhone.getServiceState() == null) {
|
|
return false;
|
|
}
|
|
return mPhone.getServiceState().isUsingNonTerrestrialNetwork();
|
|
}
|
|
|
|
private static void log(String msg) {
|
|
if (DBG) Rlog.d(TAG, msg);
|
|
}
|
|
|
|
private static void loge(String msg) {
|
|
Rlog.e(TAG, msg);
|
|
}
|
|
|
|
/** Print to both Radio log and LocalLog, used only for critical but non-sensitive msg. */
|
|
private void localLog(String msg) {
|
|
Rlog.d(TAG, msg);
|
|
mLocalLog.log(TAG + ": " + msg);
|
|
}
|
|
}
|