1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.telephony.satellite.metrics; 18 19 import android.annotation.NonNull; 20 import android.app.usage.NetworkStats; 21 import android.app.usage.NetworkStatsManager; 22 import android.content.Context; 23 import android.net.NetworkTemplate; 24 import android.os.SystemClock; 25 import android.telephony.CellInfo; 26 import android.telephony.CellSignalStrength; 27 import android.telephony.CellSignalStrengthLte; 28 import android.telephony.NetworkRegistrationInfo; 29 import android.telephony.ServiceState; 30 import android.telephony.SignalStrength; 31 import android.telephony.TelephonyManager; 32 import android.text.TextUtils; 33 import android.util.Log; 34 import android.util.SparseArray; 35 36 import com.android.internal.telephony.MccTable; 37 import com.android.internal.telephony.Phone; 38 import com.android.internal.telephony.metrics.SatelliteStats; 39 import com.android.internal.telephony.satellite.SatelliteConstants; 40 import com.android.internal.telephony.satellite.SatelliteServiceUtils; 41 import com.android.internal.telephony.subscription.SubscriptionInfoInternal; 42 import com.android.internal.telephony.subscription.SubscriptionManagerService; 43 44 import java.util.ArrayList; 45 import java.util.Arrays; 46 import java.util.Collections; 47 import java.util.List; 48 import java.util.OptionalDouble; 49 import java.util.Set; 50 51 public class CarrierRoamingSatelliteSessionStats { 52 private static final String TAG = CarrierRoamingSatelliteSessionStats.class.getSimpleName(); 53 private static final SparseArray<CarrierRoamingSatelliteSessionStats> 54 sCarrierRoamingSatelliteSessionStats = new SparseArray<>(); 55 @NonNull private final SubscriptionManagerService mSubscriptionManagerService; 56 private int mCarrierId; 57 private boolean mIsNtnRoamingInHomeCountry; 58 private int mCountOfIncomingSms; 59 private int mCountOfOutgoingSms; 60 private int mCountOfIncomingMms; 61 private int mCountOfOutgoingMms; 62 private long mIncomingMessageId; 63 private int mSessionStartTimeSec; 64 private SatelliteConnectionTimes mSatelliteConnectionTimes; 65 private List<SatelliteConnectionTimes> mSatelliteConnectionTimesList; 66 private List<Integer> mRsrpList; 67 private List<Integer> mRssnrList; 68 private int[] mSupportedSatelliteServices; 69 private int mServiceDataPolicy; 70 private Phone mPhone; 71 private Context mContext; 72 private long mSatelliteDataConsumedBytes = 0L; 73 private long mDataUsageOnSessionStartBytes = 0L; 74 CarrierRoamingSatelliteSessionStats(int subId)75 public CarrierRoamingSatelliteSessionStats(int subId) { 76 logd("Create new CarrierRoamingSatelliteSessionStats. subId=" + subId); 77 initializeParams(); 78 mSubscriptionManagerService = SubscriptionManagerService.getInstance(); 79 } 80 81 /** Gets a CarrierRoamingSatelliteSessionStats instance. */ getInstance(int subId)82 public static CarrierRoamingSatelliteSessionStats getInstance(int subId) { 83 synchronized (sCarrierRoamingSatelliteSessionStats) { 84 if (sCarrierRoamingSatelliteSessionStats.get(subId) == null) { 85 sCarrierRoamingSatelliteSessionStats.put(subId, 86 new CarrierRoamingSatelliteSessionStats(subId)); 87 } 88 return sCarrierRoamingSatelliteSessionStats.get(subId); 89 } 90 } 91 92 /** Log carrier roaming satellite session start */ onSessionStart(int carrierId, Phone phone, int[] supportedServices, int serviceDataPolicy)93 public void onSessionStart(int carrierId, Phone phone, int[] supportedServices, 94 int serviceDataPolicy) { 95 mPhone = phone; 96 mContext = mPhone.getContext(); 97 mCarrierId = carrierId; 98 mSupportedSatelliteServices = supportedServices; 99 mServiceDataPolicy = serviceDataPolicy; 100 mSessionStartTimeSec = getElapsedRealtimeInSec(); 101 mIsNtnRoamingInHomeCountry = false; 102 onConnectionStart(mPhone); 103 mDataUsageOnSessionStartBytes = getDataUsage(); 104 logd("current data consumed: " + mDataUsageOnSessionStartBytes); 105 } 106 107 /** Log carrier roaming satellite connection start */ onConnectionStart(Phone phone)108 public void onConnectionStart(Phone phone) { 109 mSatelliteConnectionTimes = new SatelliteConnectionTimes(getElapsedRealtime()); 110 updateNtnRoamingInHomeCountry(phone); 111 } 112 113 /** calculate total satellite data consumed at the session */ getDataUsage()114 private long getDataUsage() { 115 if (mContext == null) { 116 return 0L; 117 } 118 119 NetworkStatsManager networkStatsManager = 120 mContext.getSystemService(NetworkStatsManager.class); 121 122 if (networkStatsManager != null) { 123 final NetworkTemplate.Builder builder = 124 new NetworkTemplate.Builder(NetworkTemplate.MATCH_MOBILE); 125 final String subscriberId = mPhone.getSubscriberId(); 126 logd("subscriber id for data consumed:" + subscriberId); 127 128 if (!TextUtils.isEmpty(subscriberId)) { 129 builder.setSubscriberIds(Set.of(subscriberId)); 130 // Consider data usage calculation of only metered capabilities / data network 131 builder.setMeteredness(android.net.NetworkStats.METERED_YES); 132 NetworkTemplate template = builder.build(); 133 final NetworkStats.Bucket ret = networkStatsManager 134 .querySummaryForDevice(template, 0L, System.currentTimeMillis()); 135 return ret.getRxBytes() + ret.getTxBytes(); 136 } 137 } 138 return 0L; 139 } 140 141 /** Log carrier roaming satellite session end */ onSessionEnd(int subId)142 public void onSessionEnd(int subId) { 143 onConnectionEnd(); 144 long dataUsageOnSessionEndBytes = getDataUsage(); 145 logd("update data consumed: " + dataUsageOnSessionEndBytes); 146 if (dataUsageOnSessionEndBytes > 0L 147 && dataUsageOnSessionEndBytes > mDataUsageOnSessionStartBytes) { 148 mSatelliteDataConsumedBytes = 149 dataUsageOnSessionEndBytes - mDataUsageOnSessionStartBytes; 150 } 151 logd("satellite data consumed at session: " + mSatelliteDataConsumedBytes); 152 reportMetrics(subId); 153 mIsNtnRoamingInHomeCountry = false; 154 mSupportedSatelliteServices = new int[0]; 155 mServiceDataPolicy = SatelliteConstants.SATELLITE_ENTITLEMENT_SERVICE_POLICY_UNKNOWN; 156 mSatelliteDataConsumedBytes = 0L; 157 mDataUsageOnSessionStartBytes = 0L; 158 } 159 160 /** Log carrier roaming satellite connection end */ onConnectionEnd()161 public void onConnectionEnd() { 162 if (mSatelliteConnectionTimes != null) { 163 mSatelliteConnectionTimes.setEndTime(getElapsedRealtime()); 164 mSatelliteConnectionTimesList.add(mSatelliteConnectionTimes); 165 mSatelliteConnectionTimes = null; 166 } else { 167 loge("onConnectionEnd: mSatelliteConnectionTimes is null"); 168 } 169 } 170 171 /** Log rsrp and rssnr when occurred the service state change with NTN is connected. */ onSignalStrength(Phone phone)172 public void onSignalStrength(Phone phone) { 173 CellSignalStrengthLte cellSignalStrengthLte = getCellSignalStrengthLte(phone); 174 int rsrp = cellSignalStrengthLte.getRsrp(); 175 int rssnr = cellSignalStrengthLte.getRssnr(); 176 if (rsrp == CellInfo.UNAVAILABLE) { 177 logd("onSignalStrength: rsrp unavailable"); 178 return; 179 } 180 if (rssnr == CellInfo.UNAVAILABLE) { 181 logd("onSignalStrength: rssnr unavailable"); 182 return; 183 } 184 mRsrpList.add(rsrp); 185 mRssnrList.add(rssnr); 186 logd("onSignalStrength : rsrp=" + rsrp + ", rssnr=" + rssnr); 187 } 188 189 /** Log incoming sms success case */ onIncomingSms(int subId)190 public void onIncomingSms(int subId) { 191 if (!isNtnConnected()) { 192 return; 193 } 194 mCountOfIncomingSms += 1; 195 logd("onIncomingSms: subId=" + subId + ", count=" + mCountOfIncomingSms); 196 } 197 198 /** Log outgoing sms success case */ onOutgoingSms(int subId)199 public void onOutgoingSms(int subId) { 200 if (!isNtnConnected()) { 201 return; 202 } 203 mCountOfOutgoingSms += 1; 204 logd("onOutgoingSms: subId=" + subId + ", count=" + mCountOfOutgoingSms); 205 } 206 207 /** Log incoming or outgoing mms success case */ onMms(boolean isIncomingMms, long messageId)208 public void onMms(boolean isIncomingMms, long messageId) { 209 if (!isNtnConnected()) { 210 return; 211 } 212 if (isIncomingMms) { 213 mIncomingMessageId = messageId; 214 mCountOfIncomingMms += 1; 215 logd("onMms: messageId=" + messageId + ", countOfIncomingMms=" + mCountOfIncomingMms); 216 } else { 217 if (mIncomingMessageId == messageId) { 218 logd("onMms: NotifyResponse ignore it."); 219 mIncomingMessageId = 0; 220 return; 221 } 222 mCountOfOutgoingMms += 1; 223 logd("onMms: countOfOutgoingMms=" + mCountOfOutgoingMms); 224 } 225 } 226 reportMetrics(int subId)227 private void reportMetrics(int subId) { 228 int totalSatelliteModeTimeSec = mSessionStartTimeSec > 0 229 ? getElapsedRealtimeInSec() - mSessionStartTimeSec : 0; 230 int numberOfSatelliteConnections = getNumberOfSatelliteConnections(); 231 232 List<Integer> connectionGapList = getSatelliteConnectionGapList( 233 numberOfSatelliteConnections); 234 int satelliteConnectionGapMinSec = 0; 235 int satelliteConnectionGapMaxSec = 0; 236 if (!connectionGapList.isEmpty()) { 237 satelliteConnectionGapMinSec = Collections.min(connectionGapList); 238 satelliteConnectionGapMaxSec = Collections.max(connectionGapList); 239 } 240 boolean isMultiSim = mSubscriptionManagerService.getActiveSubIdList(true).length > 1; 241 242 SatelliteStats.CarrierRoamingSatelliteSessionParams params = 243 new SatelliteStats.CarrierRoamingSatelliteSessionParams.Builder() 244 .setCarrierId(mCarrierId) 245 .setIsNtnRoamingInHomeCountry(mIsNtnRoamingInHomeCountry) 246 .setTotalSatelliteModeTimeSec(totalSatelliteModeTimeSec) 247 .setNumberOfSatelliteConnections(numberOfSatelliteConnections) 248 .setAvgDurationOfSatelliteConnectionSec( 249 getAvgDurationOfSatelliteConnection()) 250 .setSatelliteConnectionGapMinSec(satelliteConnectionGapMinSec) 251 .setSatelliteConnectionGapAvgSec(getAvg(connectionGapList)) 252 .setSatelliteConnectionGapMaxSec(satelliteConnectionGapMaxSec) 253 .setRsrpAvg(getAvg(mRsrpList)) 254 .setRsrpMedian(getMedian(mRsrpList)) 255 .setRssnrAvg(getAvg(mRssnrList)) 256 .setRssnrMedian(getMedian(mRssnrList)) 257 .setCountOfIncomingSms(mCountOfIncomingSms) 258 .setCountOfOutgoingSms(mCountOfOutgoingSms) 259 .setCountOfIncomingMms(mCountOfIncomingMms) 260 .setCountOfOutgoingMms(mCountOfOutgoingMms) 261 .setSupportedSatelliteServices(mSupportedSatelliteServices) 262 .setServiceDataPolicy(mServiceDataPolicy) 263 .setSatelliteDataConsumedBytes(mSatelliteDataConsumedBytes) 264 .setIsMultiSim(isMultiSim) 265 .setIsNbIotNtn(SatelliteServiceUtils.isNbIotNtn(subId)) 266 .build(); 267 SatelliteStats.getInstance().onCarrierRoamingSatelliteSessionMetrics(params); 268 logd("Supported satellite services: " + Arrays.toString(mSupportedSatelliteServices)); 269 logd("reportMetrics: " + params); 270 initializeParams(); 271 } 272 initializeParams()273 private void initializeParams() { 274 mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID; 275 mIsNtnRoamingInHomeCountry = false; 276 mCountOfIncomingSms = 0; 277 mCountOfOutgoingSms = 0; 278 mCountOfIncomingMms = 0; 279 mCountOfOutgoingMms = 0; 280 mIncomingMessageId = 0; 281 282 mSessionStartTimeSec = 0; 283 mSatelliteConnectionTimes = null; 284 mSatelliteConnectionTimesList = new ArrayList<>(); 285 mRsrpList = new ArrayList<>(); 286 mRssnrList = new ArrayList<>(); 287 logd("initializeParams"); 288 } 289 getCellSignalStrengthLte(Phone phone)290 private CellSignalStrengthLte getCellSignalStrengthLte(Phone phone) { 291 SignalStrength signalStrength = phone.getSignalStrength(); 292 List<CellSignalStrength> cellSignalStrengths = signalStrength.getCellSignalStrengths(); 293 for (CellSignalStrength cellSignalStrength : cellSignalStrengths) { 294 if (cellSignalStrength instanceof CellSignalStrengthLte) { 295 return (CellSignalStrengthLte) cellSignalStrength; 296 } 297 } 298 299 return new CellSignalStrengthLte(); 300 } 301 getNumberOfSatelliteConnections()302 private int getNumberOfSatelliteConnections() { 303 return mSatelliteConnectionTimesList.size(); 304 } 305 getAvgDurationOfSatelliteConnection()306 private int getAvgDurationOfSatelliteConnection() { 307 if (mSatelliteConnectionTimesList.isEmpty()) { 308 return 0; 309 } 310 311 OptionalDouble averageDuration = mSatelliteConnectionTimesList.stream() 312 .filter(SatelliteConnectionTimes::isValid) 313 .mapToLong(SatelliteConnectionTimes::getDuration) 314 .average(); 315 316 return (int) (averageDuration.isPresent() ? averageDuration.getAsDouble() / 1000 : 0); 317 } 318 getSatelliteConnectionGapList(int numberOfSatelliteConnections)319 private List<Integer> getSatelliteConnectionGapList(int numberOfSatelliteConnections) { 320 if (mSatelliteConnectionTimesList.size() < 2) { 321 return new ArrayList<>(); 322 } 323 324 List<Integer> connectionGapList = new ArrayList<>(); 325 for (int i = 1; i < mSatelliteConnectionTimesList.size(); i++) { 326 SatelliteConnectionTimes prevConnection = 327 mSatelliteConnectionTimesList.get(i - 1); 328 SatelliteConnectionTimes currentConnection = 329 mSatelliteConnectionTimesList.get(i); 330 331 if (prevConnection.getEndTime() > 0 332 && currentConnection.getStartTime() > prevConnection.getEndTime()) { 333 int gap = (int) ((currentConnection.getStartTime() - prevConnection.getEndTime()) 334 / 1000); 335 connectionGapList.add(gap); 336 } 337 } 338 return connectionGapList; 339 } 340 getAvg(@onNull List<Integer> list)341 private int getAvg(@NonNull List<Integer> list) { 342 if (list.isEmpty()) { 343 return 0; 344 } 345 346 int total = 0; 347 for (int num : list) { 348 total += num; 349 } 350 351 return total / list.size(); 352 } 353 getMedian(@onNull List<Integer> list)354 private int getMedian(@NonNull List<Integer> list) { 355 if (list.isEmpty()) { 356 return 0; 357 } 358 int size = list.size(); 359 if (size == 1) { 360 return list.get(0); 361 } 362 363 Collections.sort(list); 364 return size % 2 == 0 ? (list.get(size / 2 - 1) + list.get(size / 2)) / 2 365 : list.get(size / 2); 366 } 367 getElapsedRealtimeInSec()368 private int getElapsedRealtimeInSec() { 369 return (int) (getElapsedRealtime() / 1000); 370 } 371 getElapsedRealtime()372 private long getElapsedRealtime() { 373 return SystemClock.elapsedRealtime(); 374 } 375 isNtnConnected()376 private boolean isNtnConnected() { 377 return mSessionStartTimeSec != 0; 378 } 379 updateNtnRoamingInHomeCountry(Phone phone)380 private void updateNtnRoamingInHomeCountry(Phone phone) { 381 int subId = phone.getSubId(); 382 ServiceState serviceState = phone.getServiceState(); 383 if (serviceState == null) { 384 logd("ServiceState is null"); 385 return; 386 } 387 388 String satelliteRegisteredPlmn = ""; 389 for (NetworkRegistrationInfo nri 390 : serviceState.getNetworkRegistrationInfoList()) { 391 if (nri.isNonTerrestrialNetwork()) { 392 satelliteRegisteredPlmn = nri.getRegisteredPlmn(); 393 } 394 } 395 396 SubscriptionInfoInternal subscriptionInfoInternal = 397 mSubscriptionManagerService.getSubscriptionInfoInternal(subId); 398 if (subscriptionInfoInternal == null) { 399 logd("SubscriptionInfoInternal is null"); 400 return; 401 } 402 String simCountry = MccTable.countryCodeForMcc(subscriptionInfoInternal.getMcc()); 403 mIsNtnRoamingInHomeCountry = true; 404 if (satelliteRegisteredPlmn != null 405 && satelliteRegisteredPlmn.length() >= 3) { 406 String satelliteRegisteredCountry = MccTable.countryCodeForMcc( 407 satelliteRegisteredPlmn.substring(0, 3)); 408 if (simCountry.equalsIgnoreCase(satelliteRegisteredCountry)) { 409 mIsNtnRoamingInHomeCountry = true; 410 } else { 411 // If device is connected to roaming non-terrestrial network, then marking as 412 // roaming in external country 413 mIsNtnRoamingInHomeCountry = false; 414 } 415 } 416 logd("updateNtnRoamingInHomeCountry: mIsNtnRoamingInHomeCountry=" 417 + mIsNtnRoamingInHomeCountry); 418 } 419 420 private static class SatelliteConnectionTimes { 421 private final long mStartTime; 422 private long mEndTime; 423 SatelliteConnectionTimes(long startTime)424 SatelliteConnectionTimes(long startTime) { 425 this.mStartTime = startTime; 426 this.mEndTime = 0; 427 } 428 setEndTime(long endTime)429 public void setEndTime(long endTime) { 430 this.mEndTime = endTime; 431 } 432 getStartTime()433 public long getStartTime() { 434 return mStartTime; 435 } 436 getEndTime()437 public long getEndTime() { 438 return mEndTime; 439 } 440 getDuration()441 public long getDuration() { 442 if (isValid()) { 443 return mEndTime - mStartTime; 444 } 445 return 0; 446 } 447 isValid()448 public boolean isValid() { 449 return mEndTime > mStartTime && mStartTime > 0; 450 } 451 } 452 logd(@onNull String log)453 private void logd(@NonNull String log) { 454 Log.d(TAG, log); 455 } 456 loge(@onNull String log)457 private void loge(@NonNull String log) { 458 Log.e(TAG, log); 459 } 460 } 461