blob: a45e43fcec98cc904a991cbed8ad6595a886e72c [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.metrics;
import android.annotation.Nullable;
import android.os.SystemClock;
import android.telephony.AccessNetworkConstants;
import android.telephony.AccessNetworkUtils;
import android.telephony.Annotation.NetworkType;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneFactory;
import com.android.internal.telephony.ServiceStateTracker;
import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch;
import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState;
import com.android.telephony.Rlog;
import java.util.concurrent.atomic.AtomicReference;
/** Tracks service state duration and switch metrics for each phone. */
public class ServiceStateStats {
private static final String TAG = ServiceStateStats.class.getSimpleName();
private final AtomicReference<TimestampedServiceState> mLastState =
new AtomicReference<>(new TimestampedServiceState(null, 0L));
private final Phone mPhone;
private final PersistAtomsStorage mStorage;
public ServiceStateStats(Phone phone) {
mPhone = phone;
mStorage = PhoneFactory.getMetricsCollector().getAtomsStorage();
}
/** Finalizes the durations of the current service state segment. */
public void conclude() {
final long now = getTimeMillis();
TimestampedServiceState lastState =
mLastState.getAndUpdate(
state -> new TimestampedServiceState(state.mServiceState, now));
addServiceState(lastState, now);
}
/** Updates the current service state. */
public void onServiceStateChanged(ServiceState serviceState) {
final long now = getTimeMillis();
if (isModemOff(serviceState)) {
// Finish the duration of last service state and mark modem off
addServiceState(mLastState.getAndSet(new TimestampedServiceState(null, now)), now);
} else {
CellularServiceState newState = new CellularServiceState();
newState.voiceRat = getVoiceRat(mPhone, serviceState);
newState.dataRat = getDataRat(serviceState);
newState.voiceRoamingType = serviceState.getVoiceRoamingType();
newState.dataRoamingType = serviceState.getDataRoamingType();
newState.isEndc = isEndc(serviceState);
newState.simSlotIndex = mPhone.getPhoneId();
newState.isMultiSim = SimSlotState.isMultiSim();
newState.carrierId = mPhone.getCarrierId();
TimestampedServiceState prevState =
mLastState.getAndSet(new TimestampedServiceState(newState, now));
addServiceStateAndSwitch(
prevState, now, getDataServiceSwitch(prevState.mServiceState, newState));
}
}
private void addServiceState(TimestampedServiceState prevState, long now) {
addServiceStateAndSwitch(prevState, now, null);
}
private void addServiceStateAndSwitch(
TimestampedServiceState prevState,
long now,
@Nullable CellularDataServiceSwitch serviceSwitch) {
if (prevState.mServiceState == null) {
// Skip duration when modem is off
return;
}
if (now >= prevState.mTimestamp) {
CellularServiceState state = copyOf(prevState.mServiceState);
state.totalTimeMillis = now - prevState.mTimestamp;
mStorage.addCellularServiceStateAndCellularDataServiceSwitch(state, serviceSwitch);
} else {
Rlog.e(TAG, "addServiceState: durationMillis<0");
}
}
@Nullable
private CellularDataServiceSwitch getDataServiceSwitch(
@Nullable CellularServiceState prevState, CellularServiceState nextState) {
// Record switch only if multi-SIM state and carrier ID are the same and data RAT differs.
if (prevState != null
&& prevState.isMultiSim == nextState.isMultiSim
&& prevState.carrierId == nextState.carrierId
&& prevState.dataRat != nextState.dataRat) {
CellularDataServiceSwitch serviceSwitch = new CellularDataServiceSwitch();
serviceSwitch.ratFrom = prevState.dataRat;
serviceSwitch.ratTo = nextState.dataRat;
serviceSwitch.isMultiSim = nextState.isMultiSim;
serviceSwitch.simSlotIndex = nextState.simSlotIndex;
serviceSwitch.carrierId = nextState.carrierId;
serviceSwitch.switchCount = 1;
return serviceSwitch;
} else {
return null;
}
}
/** Returns the service state for the given phone, or {@code null} if it cannot be obtained. */
@Nullable
private static ServiceState getServiceStateForPhone(Phone phone) {
ServiceStateTracker serviceStateTracker = phone.getServiceStateTracker();
return serviceStateTracker != null ? serviceStateTracker.getServiceState() : null;
}
/**
* Returns the band used from the given phone and RAT, or {@code 0} if it is invalid or cannot
* be determined.
*/
static int getBand(Phone phone, @NetworkType int rat) {
ServiceState serviceState = getServiceStateForPhone(phone);
return getBand(serviceState, rat);
}
/**
* Returns the band used from the given service state and RAT, or {@code 0} if it is invalid or
* cannot be determined.
*/
static int getBand(@Nullable ServiceState serviceState, @NetworkType int rat) {
if (serviceState == null) {
return 0; // Band unknown
}
int chNumber = serviceState.getChannelNumber();
int band;
switch (rat) {
case TelephonyManager.NETWORK_TYPE_GSM:
case TelephonyManager.NETWORK_TYPE_GPRS:
case TelephonyManager.NETWORK_TYPE_EDGE:
band = AccessNetworkUtils.getOperatingBandForArfcn(chNumber);
break;
case TelephonyManager.NETWORK_TYPE_UMTS:
case TelephonyManager.NETWORK_TYPE_HSDPA:
case TelephonyManager.NETWORK_TYPE_HSUPA:
case TelephonyManager.NETWORK_TYPE_HSPA:
case TelephonyManager.NETWORK_TYPE_HSPAP:
band = AccessNetworkUtils.getOperatingBandForUarfcn(chNumber);
break;
case TelephonyManager.NETWORK_TYPE_LTE:
case TelephonyManager.NETWORK_TYPE_LTE_CA:
band = AccessNetworkUtils.getOperatingBandForEarfcn(chNumber);
break;
default:
band = 0;
break;
}
return band == AccessNetworkUtils.INVALID_BAND ? 0 : band;
}
private static CellularServiceState copyOf(CellularServiceState state) {
// MessageNano does not support clone, have to copy manually
CellularServiceState copy = new CellularServiceState();
copy.voiceRat = state.voiceRat;
copy.dataRat = state.dataRat;
copy.voiceRoamingType = state.voiceRoamingType;
copy.dataRoamingType = state.dataRoamingType;
copy.isEndc = state.isEndc;
copy.simSlotIndex = state.simSlotIndex;
copy.isMultiSim = state.isMultiSim;
copy.carrierId = state.carrierId;
copy.totalTimeMillis = state.totalTimeMillis;
return copy;
}
/**
* Returns {@code true} if modem radio is turned off (e.g. airplane mode).
*
* <p>Currently this is approximated by voice service state being {@code STATE_POWER_OFF}.
*/
private static boolean isModemOff(ServiceState state) {
// TODO(b/189335473): we should get this info from phone's radio power state, which is
// updated separately
return state.getVoiceRegState() == ServiceState.STATE_POWER_OFF;
}
private static @NetworkType int getVoiceRat(Phone phone, ServiceState state) {
boolean isWifiCall =
phone.getImsPhone() != null
&& phone.getImsPhone().isWifiCallingEnabled()
&& state.getDataNetworkType() == TelephonyManager.NETWORK_TYPE_IWLAN;
return isWifiCall ? TelephonyManager.NETWORK_TYPE_IWLAN : state.getVoiceNetworkType();
}
private static @NetworkType int getDataRat(ServiceState state) {
final NetworkRegistrationInfo wwanRegInfo =
state.getNetworkRegistrationInfo(
NetworkRegistrationInfo.DOMAIN_PS,
AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
return wwanRegInfo != null
? wwanRegInfo.getAccessNetworkTechnology()
: TelephonyManager.NETWORK_TYPE_UNKNOWN;
}
private static boolean isEndc(ServiceState state) {
if (getDataRat(state) != TelephonyManager.NETWORK_TYPE_LTE) {
return false;
}
int nrState = state.getNrState();
return nrState == NetworkRegistrationInfo.NR_STATE_CONNECTED
|| nrState == NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED;
}
@VisibleForTesting
protected long getTimeMillis() {
return SystemClock.elapsedRealtime();
}
private static final class TimestampedServiceState {
private final CellularServiceState mServiceState;
private final long mTimestamp; // Start time of the service state segment
TimestampedServiceState(CellularServiceState serviceState, long timestamp) {
mServiceState = serviceState;
mTimestamp = timestamp;
}
}
}