blob: e3e217244d16214a07fc31d32547d9d968f69c1d [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.telephony;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.AsyncResult;
import android.os.Message;
import android.os.PersistableBundle;
import android.telephony.AccessNetworkConstants;
import android.telephony.Annotation;
import android.telephony.CarrierConfigManager;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyDisplayInfo;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import com.android.internal.util.IState;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import com.android.telephony.Rlog;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* The NetworkTypeController evaluates the override network type of {@link TelephonyDisplayInfo}
* and sends it to {@link DisplayInfoController}. The override network type can replace the signal
* icon displayed on the status bar. It is affected by changes in data RAT, NR state, NR frequency,
* data activity, physical channel config, and carrier configurations. Based on carrier configs,
* NetworkTypeController also allows timers between various 5G states to prevent flickering.
*/
public class NetworkTypeController extends StateMachine {
private static final boolean DBG = true;
private static final String TAG = "NetworkTypeController";
private static final String ICON_5G = "5g";
private static final String ICON_5G_PLUS = "5g_plus";
private static final String STATE_CONNECTED_MMWAVE = "connected_mmwave";
private static final String STATE_CONNECTED = "connected";
private static final String STATE_NOT_RESTRICTED_RRC_IDLE = "not_restricted_rrc_idle";
private static final String STATE_NOT_RESTRICTED_RRC_CON = "not_restricted_rrc_con";
private static final String STATE_RESTRICTED = "restricted";
private static final String STATE_ANY = "any";
private static final String STATE_LEGACY = "legacy";
private static final String[] ALL_STATES = { STATE_CONNECTED_MMWAVE, STATE_CONNECTED,
STATE_NOT_RESTRICTED_RRC_IDLE, STATE_NOT_RESTRICTED_RRC_CON, STATE_RESTRICTED,
STATE_LEGACY };
/** Stop all timers and go to current state. */
public static final int EVENT_UPDATE = 0;
/** Quit after processing all existing messages. */
public static final int EVENT_QUIT = 1;
private static final int EVENT_DATA_RAT_CHANGED = 2;
private static final int EVENT_NR_STATE_CHANGED = 3;
private static final int EVENT_NR_FREQUENCY_CHANGED = 4;
private static final int EVENT_DATA_ACTIVITY_CHANGED = 5;
private static final int EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED = 6;
private static final int EVENT_CARRIER_CONFIG_CHANGED = 7;
private static final int EVENT_PRIMARY_TIMER_EXPIRED = 8;
private static final int EVENT_SECONDARY_TIMER_EXPIRED = 9;
private static final int[] ALL_EVENTS = { EVENT_DATA_RAT_CHANGED, EVENT_NR_STATE_CHANGED,
EVENT_NR_FREQUENCY_CHANGED, EVENT_DATA_ACTIVITY_CHANGED,
EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED, EVENT_CARRIER_CONFIG_CHANGED,
EVENT_PRIMARY_TIMER_EXPIRED, EVENT_SECONDARY_TIMER_EXPIRED };
private static final String[] sEvents = new String[EVENT_SECONDARY_TIMER_EXPIRED + 1];
static {
sEvents[EVENT_UPDATE] = "EVENT_UPDATE";
sEvents[EVENT_QUIT] = "EVENT_QUIT";
sEvents[EVENT_DATA_RAT_CHANGED] = "EVENT_DATA_RAT_CHANGED";
sEvents[EVENT_NR_STATE_CHANGED] = "EVENT_NR_STATE_CHANGED";
sEvents[EVENT_NR_FREQUENCY_CHANGED] = "EVENT_NR_FREQUENCY_CHANGED";
sEvents[EVENT_DATA_ACTIVITY_CHANGED] = "EVENT_DATA_ACTIVITY_CHANGED";
sEvents[EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED] =
"EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED";
sEvents[EVENT_CARRIER_CONFIG_CHANGED] = "EVENT_CARRIER_CONFIG_CHANGED";
sEvents[EVENT_PRIMARY_TIMER_EXPIRED] = "EVENT_PRIMARY_TIMER_EXPIRED";
sEvents[EVENT_SECONDARY_TIMER_EXPIRED] = "EVENT_SECONDARY_TIMER_EXPIRED";
}
private final Phone mPhone;
private final DisplayInfoController mDisplayInfoController;
private final TelephonyManager mTelephonyManager;
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
&& intent.getIntExtra(SubscriptionManager.EXTRA_SLOT_INDEX,
SubscriptionManager.INVALID_PHONE_INDEX) == mPhone.getPhoneId()
&& !intent.getBooleanExtra(CarrierConfigManager.EXTRA_REBROADCAST_ON_UNLOCK,
false)) {
sendMessage(EVENT_CARRIER_CONFIG_CHANGED);
}
}
};
private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
@Override
public void onDataActivity(int direction) {
sendMessage(EVENT_DATA_ACTIVITY_CHANGED);
}
};
private Map<String, OverrideTimerRule> mOverrideTimerRules = new HashMap<>();
private String mLteEnhancedPattern = "";
private int mOverrideNetworkType;
private boolean mIsPhysicalChannelConfigOn;
private boolean mIsPrimaryTimerActive;
private boolean mIsSecondaryTimerActive;
private String mPrimaryTimerState;
private String mSecondaryTimerState;
private String mPreviousState;
/**
* NetworkTypeController constructor.
*
* @param phone Phone object.
* @param displayInfoController DisplayInfoController to send override network types to.
*/
public NetworkTypeController(Phone phone, DisplayInfoController displayInfoController) {
super(TAG, displayInfoController);
mPhone = phone;
mDisplayInfoController = displayInfoController;
mTelephonyManager = TelephonyManager.from(phone.getContext())
.createForSubscriptionId(phone.getSubId());
mOverrideNetworkType = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
mIsPhysicalChannelConfigOn = true;
addState(mDefaultState);
addState(mLegacyState, mDefaultState);
addState(mIdleState, mDefaultState);
addState(mLteConnectedState, mDefaultState);
addState(mNrConnectedState, mDefaultState);
setInitialState(mDefaultState);
registerForAllEvents();
parseCarrierConfigs();
start();
}
/**
* @return The current override network type, used to create TelephonyDisplayInfo in
* DisplayInfoController.
*/
public @Annotation.OverrideNetworkType int getOverrideNetworkType() {
return mOverrideNetworkType;
}
private void registerForAllEvents() {
mPhone.getServiceStateTracker().registerForDataRegStateOrRatChanged(
AccessNetworkConstants.TRANSPORT_TYPE_WWAN, getHandler(),
EVENT_DATA_RAT_CHANGED, null);
mPhone.getServiceStateTracker().registerForNrStateChanged(getHandler(),
EVENT_NR_STATE_CHANGED, null);
mPhone.getServiceStateTracker().registerForNrFrequencyChanged(getHandler(),
EVENT_NR_FREQUENCY_CHANGED, null);
mPhone.getDeviceStateMonitor().registerForPhysicalChannelConfigNotifChanged(getHandler(),
EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED, null);
IntentFilter filter = new IntentFilter();
filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
mPhone.getContext().registerReceiver(mIntentReceiver, filter, null, mPhone);
if (mTelephonyManager != null) {
mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_DATA_ACTIVITY);
}
}
private void unRegisterForAllEvents() {
mPhone.getServiceStateTracker().unregisterForDataRegStateOrRatChanged(
AccessNetworkConstants.TRANSPORT_TYPE_WWAN, getHandler());
mPhone.getServiceStateTracker().unregisterForNrStateChanged(getHandler());
mPhone.getServiceStateTracker().unregisterForNrFrequencyChanged(getHandler());
mPhone.mDeviceStateMonitor.unregisterForPhysicalChannelConfigNotifChanged(getHandler());
mPhone.getContext().unregisterReceiver(mIntentReceiver);
if (mTelephonyManager != null) {
mTelephonyManager.listen(mPhoneStateListener, 0);
}
}
private void parseCarrierConfigs() {
String nrIconConfiguration = CarrierConfigManager.getDefaultConfig().getString(
CarrierConfigManager.KEY_5G_ICON_CONFIGURATION_STRING);
String overrideTimerRule = CarrierConfigManager.getDefaultConfig().getString(
CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING);
String overrideSecondaryTimerRule = CarrierConfigManager.getDefaultConfig().getString(
CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING);
mLteEnhancedPattern = CarrierConfigManager.getDefaultConfig().getString(
CarrierConfigManager.KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING);
CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
.getSystemService(Context.CARRIER_CONFIG_SERVICE);
if (configManager != null) {
PersistableBundle b = configManager.getConfigForSubId(mPhone.getSubId());
if (b != null) {
if (b.getString(CarrierConfigManager.KEY_5G_ICON_CONFIGURATION_STRING) != null) {
nrIconConfiguration = b.getString(
CarrierConfigManager.KEY_5G_ICON_CONFIGURATION_STRING);
}
if (b.getString(CarrierConfigManager
.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING) != null) {
overrideTimerRule = b.getString(
CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING);
}
if (b.getString(CarrierConfigManager
.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING) != null) {
overrideSecondaryTimerRule = b.getString(
CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING);
}
if (b.getString(CarrierConfigManager
.KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING) != null) {
mLteEnhancedPattern = b.getString(
CarrierConfigManager.KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING);
}
}
}
createTimerRules(nrIconConfiguration, overrideTimerRule, overrideSecondaryTimerRule);
}
private void createTimerRules(String icons, String timers, String secondaryTimers) {
Map<String, OverrideTimerRule> tempRules = new HashMap<>();
if (!TextUtils.isEmpty(icons)) {
// Format: "STATE:ICON,STATE2:ICON2"
for (String pair : icons.trim().split(",")) {
String[] kv = (pair.trim().toLowerCase()).split(":");
if (kv.length != 2) {
if (DBG) loge("Invalid 5G icon configuration, config = " + pair);
continue;
}
int icon = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
if (kv[1].equals(ICON_5G)) {
icon = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA;
} else if (kv[1].equals(ICON_5G_PLUS)) {
icon = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE;
} else {
if (DBG) loge("Invalid 5G icon = " + kv[1]);
}
tempRules.put(kv[0], new OverrideTimerRule(kv[0], icon));
}
}
// Ensure all states have an associated OverrideTimerRule and icon
for (String state : ALL_STATES) {
if (!tempRules.containsKey(state)) {
tempRules.put(state, new OverrideTimerRule(
state, TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE));
}
}
if (!TextUtils.isEmpty(timers)) {
// Format: "FROM_STATE,TO_STATE,DURATION;FROM_STATE_2,TO_STATE_2,DURATION_2"
for (String triple : timers.trim().split(";")) {
String[] kv = (triple.trim().toLowerCase()).split(",");
if (kv.length != 3) {
if (DBG) loge("Invalid 5G icon timer configuration, config = " + triple);
continue;
}
int duration;
try {
duration = Integer.parseInt(kv[2]);
} catch (NumberFormatException e) {
continue;
}
if (kv[0].equals(STATE_ANY)) {
for (String state : ALL_STATES) {
OverrideTimerRule node = tempRules.get(state);
node.addTimer(kv[1], duration);
}
} else {
OverrideTimerRule node = tempRules.get(kv[0]);
node.addTimer(kv[1], duration);
}
}
}
if (!TextUtils.isEmpty(secondaryTimers)) {
// Format: "PRIMARY_STATE,TO_STATE,DURATION;PRIMARY_STATE_2,TO_STATE_2,DURATION_2"
for (String triple : secondaryTimers.trim().split(";")) {
String[] kv = (triple.trim().toLowerCase()).split(",");
if (kv.length != 3) {
if (DBG) {
loge("Invalid 5G icon secondary timer configuration, config = " + triple);
}
continue;
}
int duration;
try {
duration = Integer.parseInt(kv[2]);
} catch (NumberFormatException e) {
continue;
}
if (kv[0].equals(STATE_ANY)) {
for (String state : ALL_STATES) {
OverrideTimerRule node = tempRules.get(state);
node.addSecondaryTimer(kv[1], duration);
}
} else {
OverrideTimerRule node = tempRules.get(kv[0]);
node.addSecondaryTimer(kv[1], duration);
}
}
}
mOverrideTimerRules = tempRules;
if (DBG) log("mOverrideTimerRules: " + mOverrideTimerRules);
}
private void updateOverrideNetworkType() {
if (mIsPrimaryTimerActive || mIsSecondaryTimerActive) {
if (DBG) log("Skip updating override network type since timer is active.");
return;
}
int displayNetworkType = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
int dataNetworkType = mPhone.getServiceState().getDataNetworkType();
// NR display is not accurate when physical channel config notifications are off
if (mIsPhysicalChannelConfigOn
&& (mPhone.getServiceState().getNrState() != NetworkRegistrationInfo.NR_STATE_NONE
|| dataNetworkType == TelephonyManager.NETWORK_TYPE_NR)) {
// Process NR display network type
displayNetworkType = getNrDisplayType();
if (displayNetworkType == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE) {
// Use LTE values if 5G values aren't defined
displayNetworkType = getLteDisplayType();
}
} else if (isLte(dataNetworkType)) {
// Process LTE display network type
displayNetworkType = getLteDisplayType();
}
mOverrideNetworkType = displayNetworkType;
mDisplayInfoController.updateTelephonyDisplayInfo();
}
private @Annotation.OverrideNetworkType int getNrDisplayType() {
// Icon display keys in order of priority
List<String> keys = new ArrayList<>();
// TODO: Update for NR SA
switch (mPhone.getServiceState().getNrState()) {
case NetworkRegistrationInfo.NR_STATE_CONNECTED:
if (isNrMmwave()) {
keys.add(STATE_CONNECTED_MMWAVE);
}
keys.add(STATE_CONNECTED);
break;
case NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED:
keys.add(isDataActive() ? STATE_NOT_RESTRICTED_RRC_CON
: STATE_NOT_RESTRICTED_RRC_IDLE);
break;
case NetworkRegistrationInfo.NR_STATE_RESTRICTED:
keys.add(STATE_RESTRICTED);
break;
}
for (String key : keys) {
OverrideTimerRule rule = mOverrideTimerRules.get(key);
if (rule != null && rule.mOverrideType
!= TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE) {
return rule.mOverrideType;
}
}
return TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
}
private @Annotation.OverrideNetworkType int getLteDisplayType() {
int value = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
if (mPhone.getServiceState().getDataNetworkType() == TelephonyManager.NETWORK_TYPE_LTE_CA
|| mPhone.getServiceState().isUsingCarrierAggregation()) {
value = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA;
}
if (isLteEnhancedAvailable()) {
value = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO;
}
return value;
}
private boolean isLteEnhancedAvailable() {
if (TextUtils.isEmpty(mLteEnhancedPattern)) {
return false;
}
Pattern stringPattern = Pattern.compile(mLteEnhancedPattern);
for (String opName : new String[] {mPhone.getServiceState().getOperatorAlphaLongRaw(),
mPhone.getServiceState().getOperatorAlphaShortRaw()}) {
if (!TextUtils.isEmpty(opName)) {
Matcher matcher = stringPattern.matcher(opName);
if (matcher.find()) {
return true;
}
}
}
return false;
}
/**
* The parent state for all other states.
*/
private final class DefaultState extends State {
@Override
public boolean processMessage(Message msg) {
if (DBG) log("DefaultState: process " + getEventName(msg.what));
switch (msg.what) {
case EVENT_UPDATE:
resetAllTimers();
transitionToCurrentState();
break;
case EVENT_QUIT:
resetAllTimers();
unRegisterForAllEvents();
quit();
break;
case EVENT_DATA_RAT_CHANGED:
case EVENT_NR_STATE_CHANGED:
case EVENT_NR_FREQUENCY_CHANGED:
case EVENT_DATA_ACTIVITY_CHANGED:
// ignored
break;
case EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED:
AsyncResult result = (AsyncResult) msg.obj;
mIsPhysicalChannelConfigOn = (boolean) result.result;
for (int event : ALL_EVENTS) {
removeMessages(event);
}
if (!mIsPhysicalChannelConfigOn) {
resetAllTimers();
}
transitionToCurrentState();
break;
case EVENT_CARRIER_CONFIG_CHANGED:
for (int event : ALL_EVENTS) {
removeMessages(event);
}
parseCarrierConfigs();
resetAllTimers();
transitionToCurrentState();
break;
case EVENT_PRIMARY_TIMER_EXPIRED:
transitionWithSecondaryTimerTo((IState) msg.obj);
break;
case EVENT_SECONDARY_TIMER_EXPIRED:
mIsSecondaryTimerActive = false;
mSecondaryTimerState = "";
updateTimers();
updateOverrideNetworkType();
break;
default:
throw new RuntimeException("Received invalid event: " + msg.what);
}
return HANDLED;
}
}
private final DefaultState mDefaultState = new DefaultState();
/**
* Device does not have NR available, due to any of the below reasons:
* <ul>
* <li> LTE cell does not support EN-DC
* <li> LTE cell supports EN-DC, but the use of NR is restricted
* <li> Data network type is not LTE, NR NSA, or NR SA
* </ul>
* This is the initial state.
*/
private final class LegacyState extends State {
@Override
public void enter() {
if (DBG) log("Entering LegacyState");
updateTimers();
updateOverrideNetworkType();
if (!mIsPrimaryTimerActive && !mIsSecondaryTimerActive) {
mPreviousState = getName();
}
}
@Override
public boolean processMessage(Message msg) {
if (DBG) log("LegacyState: process " + getEventName(msg.what));
updateTimers();
int rat = mPhone.getServiceState().getDataNetworkType();
switch (msg.what) {
case EVENT_DATA_RAT_CHANGED:
if (rat == TelephonyManager.NETWORK_TYPE_NR || isLte(rat) && isNrConnected()) {
transitionTo(mNrConnectedState);
} else if (isLte(rat) && isNrNotRestricted()) {
transitionWithTimerTo(isDataActive() ? mLteConnectedState : mIdleState);
}
break;
case EVENT_NR_STATE_CHANGED:
if (isNrConnected()) {
transitionTo(mNrConnectedState);
} else if (isLte(rat) && isNrNotRestricted()) {
transitionWithTimerTo(isDataActive() ? mLteConnectedState : mIdleState);
} else if (isLte(rat) && isNrRestricted()) {
updateOverrideNetworkType();
}
break;
case EVENT_NR_FREQUENCY_CHANGED:
case EVENT_DATA_ACTIVITY_CHANGED:
// ignored
break;
default:
return NOT_HANDLED;
}
if (!mIsPrimaryTimerActive && !mIsSecondaryTimerActive) {
mPreviousState = getName();
}
return HANDLED;
}
@Override
public String getName() {
return isNrRestricted() ? STATE_RESTRICTED : STATE_LEGACY;
}
}
private final LegacyState mLegacyState = new LegacyState();
/**
* Device does not have any physical connection with the cell (RRC idle).
*/
private final class IdleState extends State {
@Override
public void enter() {
if (DBG) log("Entering IdleState");
updateTimers();
updateOverrideNetworkType();
if (!mIsPrimaryTimerActive && !mIsSecondaryTimerActive) {
mPreviousState = getName();
}
}
@Override
public boolean processMessage(Message msg) {
if (DBG) log("IdleState: process " + getEventName(msg.what));
updateTimers();
switch (msg.what) {
case EVENT_DATA_RAT_CHANGED:
int rat = mPhone.getServiceState().getDataNetworkType();
if (rat == TelephonyManager.NETWORK_TYPE_NR) {
transitionTo(mNrConnectedState);
} else if (!isLte(rat) || !isNrNotRestricted()) {
transitionWithTimerTo(mLegacyState);
}
break;
case EVENT_NR_STATE_CHANGED:
if (isNrConnected()) {
transitionTo(mNrConnectedState);
} else if (!isNrNotRestricted()) {
transitionWithTimerTo(mLegacyState);
}
break;
case EVENT_NR_FREQUENCY_CHANGED:
// ignore
break;
case EVENT_DATA_ACTIVITY_CHANGED:
if (isDataActive()) {
transitionWithTimerTo(mLteConnectedState);
}
break;
default:
return NOT_HANDLED;
}
if (!mIsPrimaryTimerActive && !mIsSecondaryTimerActive) {
mPreviousState = getName();
}
return HANDLED;
}
@Override
public String getName() {
return STATE_NOT_RESTRICTED_RRC_IDLE;
}
}
private final IdleState mIdleState = new IdleState();
/**
* Device is connected to LTE as the primary cell (RRC connected).
*/
private final class LteConnectedState extends State {
@Override
public void enter() {
if (DBG) log("Entering LteConnectedState");
updateTimers();
updateOverrideNetworkType();
if (!mIsPrimaryTimerActive && !mIsSecondaryTimerActive) {
mPreviousState = getName();
}
}
@Override
public boolean processMessage(Message msg) {
if (DBG) log("LteConnectedState: process " + getEventName(msg.what));
updateTimers();
switch (msg.what) {
case EVENT_DATA_RAT_CHANGED:
int rat = mPhone.getServiceState().getDataNetworkType();
if (rat == TelephonyManager.NETWORK_TYPE_NR) {
transitionTo(mNrConnectedState);
} else if (!isLte(rat) || !isNrNotRestricted()) {
transitionWithTimerTo(mLegacyState);
}
break;
case EVENT_NR_STATE_CHANGED:
if (isNrConnected()) {
transitionTo(mNrConnectedState);
} else if (!isNrNotRestricted()) {
transitionWithTimerTo(mLegacyState);
}
break;
case EVENT_NR_FREQUENCY_CHANGED:
// ignore
break;
case EVENT_DATA_ACTIVITY_CHANGED:
if (!isDataActive()) {
transitionWithTimerTo(mIdleState);
}
break;
default:
return NOT_HANDLED;
}
if (!mIsPrimaryTimerActive && !mIsSecondaryTimerActive) {
mPreviousState = getName();
}
return HANDLED;
}
@Override
public String getName() {
return STATE_NOT_RESTRICTED_RRC_CON;
}
}
private final LteConnectedState mLteConnectedState = new LteConnectedState();
/**
* Device is connected to 5G NR as the secondary cell.
*/
private final class NrConnectedState extends State {
@Override
public void enter() {
if (DBG) log("Entering NrConnectedState");
updateTimers();
updateOverrideNetworkType();
if (!mIsPrimaryTimerActive && !mIsSecondaryTimerActive) {
mPreviousState = getName();
}
}
@Override
public boolean processMessage(Message msg) {
if (DBG) log("NrConnectedState: process " + getEventName(msg.what));
updateTimers();
int rat = mPhone.getServiceState().getDataNetworkType();
switch (msg.what) {
case EVENT_DATA_RAT_CHANGED:
if (rat == TelephonyManager.NETWORK_TYPE_NR || isLte(rat) && isNrConnected()) {
updateOverrideNetworkType();
} else if (isLte(rat) && isNrNotRestricted()) {
transitionWithTimerTo(isDataActive() ? mLteConnectedState : mIdleState);
} else {
transitionWithTimerTo(mLegacyState);
}
break;
case EVENT_NR_STATE_CHANGED:
if (isLte(rat) && isNrNotRestricted()) {
transitionWithTimerTo(isDataActive() ? mLteConnectedState : mIdleState);
} else if (rat != TelephonyManager.NETWORK_TYPE_NR && !isNrConnected()) {
transitionWithTimerTo(mLegacyState);
}
break;
case EVENT_NR_FREQUENCY_CHANGED:
if (!isNrMmwave()) {
transitionWithTimerTo(mNrConnectedState);
} else {
updateOverrideNetworkType();
}
break;
case EVENT_DATA_ACTIVITY_CHANGED:
// ignore
break;
default:
return NOT_HANDLED;
}
if (!mIsPrimaryTimerActive && !mIsSecondaryTimerActive) {
mPreviousState = getName();
}
return HANDLED;
}
@Override
public String getName() {
return isNrMmwave() ? STATE_CONNECTED_MMWAVE : STATE_CONNECTED;
}
}
private final NrConnectedState mNrConnectedState = new NrConnectedState();
private void transitionWithTimerTo(IState destState) {
String destName = destState.getName();
OverrideTimerRule rule = mOverrideTimerRules.get(mPreviousState);
if (rule != null && rule.getTimer(destName) > 0) {
if (DBG) log("Primary timer started for state: " + mPreviousState);
mPrimaryTimerState = mPreviousState;
mPreviousState = getCurrentState().getName();
mIsPrimaryTimerActive = true;
sendMessageDelayed(EVENT_PRIMARY_TIMER_EXPIRED, destState,
rule.getTimer(destName) * 1000);
}
transitionTo(destState);
}
private void transitionWithSecondaryTimerTo(IState destState) {
String destName = destState.getName();
OverrideTimerRule rule = mOverrideTimerRules.get(mPrimaryTimerState);
if (rule != null && rule.getSecondaryTimer(destName) > 0) {
String currentName = getCurrentState().getName();
if (DBG) log("Secondary timer started for state: " + currentName);
mSecondaryTimerState = currentName;
mPreviousState = currentName;
mIsSecondaryTimerActive = true;
sendMessageDelayed(EVENT_SECONDARY_TIMER_EXPIRED, destState,
rule.getSecondaryTimer(destName) * 1000);
}
mIsPrimaryTimerActive = false;
transitionTo(destState);
}
private void transitionToCurrentState() {
int dataRat = mPhone.getServiceState().getDataNetworkType();
if (dataRat == TelephonyManager.NETWORK_TYPE_NR || isNrConnected()) {
transitionTo(mNrConnectedState);
mPreviousState = isNrMmwave() ? STATE_CONNECTED_MMWAVE : STATE_CONNECTED;
} else if (isLte(dataRat) && isNrNotRestricted()) {
if (isDataActive()) {
transitionTo(mLteConnectedState);
mPreviousState = STATE_NOT_RESTRICTED_RRC_CON;
} else {
transitionTo(mIdleState);
mPreviousState = STATE_NOT_RESTRICTED_RRC_IDLE;
}
} else {
transitionTo(mLegacyState);
mPreviousState = isNrRestricted() ? STATE_RESTRICTED : STATE_LEGACY;
}
}
private void updateTimers() {
String currentState = getCurrentState().getName();
if (mIsPrimaryTimerActive && mPrimaryTimerState.equals(currentState)) {
// remove primary timer if device goes back to the original state
if (DBG) {
log("Remove primary timer since primary state and current state equal: "
+ mPrimaryTimerState);
}
removeMessages(EVENT_PRIMARY_TIMER_EXPIRED);
mIsPrimaryTimerActive = false;
mPrimaryTimerState = "";
}
if (mIsSecondaryTimerActive && !mSecondaryTimerState.equals(currentState)) {
// remove secondary timer if devices is no longer in secondary timer state
if (DBG) {
log("Remove secondary timer since current state (" + currentState
+ ") is no longer secondary timer state (" + mSecondaryTimerState + ").");
}
removeMessages(EVENT_SECONDARY_TIMER_EXPIRED);
mIsSecondaryTimerActive = false;
mSecondaryTimerState = "";
}
}
private void resetAllTimers() {
removeMessages(EVENT_PRIMARY_TIMER_EXPIRED);
removeMessages(EVENT_SECONDARY_TIMER_EXPIRED);
mIsPrimaryTimerActive = false;
mIsSecondaryTimerActive = false;
mPrimaryTimerState = "";
mSecondaryTimerState = "";
}
/**
* Private class defining timer rules between states to prevent flickering. These rules are
* created in {@link #parseCarrierConfigs()} based on various carrier configs.
*/
private class OverrideTimerRule {
/** The 5G state this timer rule applies for. See {@link #ALL_STATES}. */
final String mState;
/**
* The override network type associated with this 5G state. This is the icon that will be
* displayed on the status bar. An override type of NONE will display the LTE value instead.
*/
final int mOverrideType;
/**
* A map of destination states and associated timers. If the 5G state changes from mState
* to the destination state, keep the override type until either the primary timer expires
* or mState is regained.
*/
final Map<String, Integer> mPrimaryTimers;
/**
* A map of secondary states and associated timers. After the primary timer expires, keep
* the override type until either the secondary timer expires or the device is no longer in
* the secondary state.
*/
final Map<String, Integer> mSecondaryTimers;
OverrideTimerRule(String state, int overrideType) {
mState = state;
mOverrideType = overrideType;
mPrimaryTimers = new HashMap<>();
mSecondaryTimers = new HashMap<>();
}
/**
* Add a primary timer.
* @param destination Transitions from mState to the destination state.
* @param duration How long to keep the override type after transition to destination state.
*/
public void addTimer(String destination, int duration) {
mPrimaryTimers.put(destination, duration);
}
/**
* Add a secondary timer
* @param secondaryState Stays in secondaryState after primary timer expires.
* @param duration How long to keep the override type while in secondaryState.
*/
public void addSecondaryTimer(String secondaryState, int duration) {
mSecondaryTimers.put(secondaryState, duration);
}
/**
* @return Primary timer duration from mState to destination state, or 0 if not defined.
*/
public int getTimer(String destination) {
Integer timer = mPrimaryTimers.get(destination);
timer = timer == null ? mPrimaryTimers.get(STATE_ANY) : timer;
return timer == null ? 0 : timer;
}
/**
* @return Secondary timer duration for secondaryState, or 0 if not defined.
*/
public int getSecondaryTimer(String secondaryState) {
Integer secondaryTimer = mSecondaryTimers.get(secondaryState);
secondaryTimer = secondaryTimer == null
? mSecondaryTimers.get(STATE_ANY) : secondaryTimer;
return secondaryTimer == null ? 0 : secondaryTimer;
}
@Override
public String toString() {
return "{mState=" + mState
+ ", mOverrideType="
+ TelephonyDisplayInfo.overrideNetworkTypeToString(mOverrideType)
+ ", mPrimaryTimers=" + mPrimaryTimers
+ ", mSecondaryTimers=" + mSecondaryTimers + "}";
}
}
private boolean isNrConnected() {
return mPhone.getServiceState().getNrState() == NetworkRegistrationInfo.NR_STATE_CONNECTED;
}
private boolean isNrNotRestricted() {
return mPhone.getServiceState().getNrState()
== NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED;
}
private boolean isNrRestricted() {
return mPhone.getServiceState().getNrState()
== NetworkRegistrationInfo.NR_STATE_RESTRICTED;
}
private boolean isNrMmwave() {
return mPhone.getServiceState().getNrFrequencyRange()
== ServiceState.FREQUENCY_RANGE_MMWAVE;
}
private boolean isLte(int rat) {
return rat == TelephonyManager.NETWORK_TYPE_LTE
|| rat == TelephonyManager.NETWORK_TYPE_LTE_CA;
}
private boolean isDataActive() {
PhoneInternalInterface.DataActivityState activity = mPhone.getDataActivityState();
return activity != PhoneInternalInterface.DataActivityState.DORMANT
&& activity != PhoneInternalInterface.DataActivityState.NONE;
}
private String getEventName(int event) {
try {
return sEvents[event];
} catch (ArrayIndexOutOfBoundsException e) {
return "EVENT_NOT_DEFINED";
}
}
protected void log(String s) {
Rlog.d(TAG, s);
}
protected void loge(String s) {
Rlog.e(TAG, s);
}
@Override
public String toString() {
return "mOverrideTimerRules=" + mOverrideTimerRules.toString()
+ ", mLteEnhancedPattern=" + mLteEnhancedPattern
+ ", mIsPhysicalChannelConfigOn=" + mIsPhysicalChannelConfigOn
+ ", mIsPrimaryTimerActive=" + mIsPrimaryTimerActive
+ ", mIsSecondaryTimerActive=" + mIsSecondaryTimerActive
+ ", mPrimaryTimerState=" + mPrimaryTimerState
+ ", mSecondaryTimerState=" + mSecondaryTimerState
+ ", mPreviousState=" + mPreviousState;
}
@Override
public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
pw.print("NetworkTypeController: ");
super.dump(fd, pw, args);
pw.flush();
pw.increaseIndent();
pw.println("mOverrideTimerRules=" + mOverrideTimerRules.toString());
pw.println("mLteEnhancedPattern=" + mLteEnhancedPattern);
pw.println("mIsPhysicalChannelConfigOn=" + mIsPhysicalChannelConfigOn);
pw.println("mIsPrimaryTimerActive=" + mIsPrimaryTimerActive);
pw.println("mIsSecondaryTimerActive=" + mIsSecondaryTimerActive);
pw.println("mPrimaryTimerState=" + mPrimaryTimerState);
pw.println("mSecondaryTimerState=" + mSecondaryTimerState);
pw.println("mPreviousState=" + mPreviousState);
pw.decreaseIndent();
pw.flush();
}
}