1 /* 2 * Copyright (C) 2023 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.content.BroadcastReceiver; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.IntentFilter; 24 import android.os.BatteryManager; 25 import android.os.SystemClock; 26 import android.telephony.satellite.SatelliteManager; 27 import android.util.Log; 28 29 import com.android.internal.annotations.VisibleForTesting; 30 import com.android.internal.telephony.metrics.SatelliteStats; 31 import com.android.internal.telephony.satellite.SatelliteServiceUtils; 32 33 /** 34 * Stats to log to satellite metrics 35 */ 36 public class ControllerMetricsStats { 37 private static final int ADD_COUNT = 1; 38 private static final String TAG = ControllerMetricsStats.class.getSimpleName(); 39 40 private static ControllerMetricsStats sInstance; 41 42 private final Context mContext; 43 private SatelliteStats mSatelliteStats; 44 45 private long mSatelliteOnTimeMillis; 46 private int mBatteryLevelWhenServiceOn; 47 private boolean mIsSatelliteModemOn; 48 private Boolean mIsBatteryCharged = null; 49 private long mBatteryChargedStartTime; 50 private int mTotalBatteryChargeTimeSec; 51 52 /** 53 * @return The singleton instance of ControllerMetricsStats. 54 */ getInstance()55 public static ControllerMetricsStats getInstance() { 56 if (sInstance == null) { 57 loge("ControllerMetricsStats was not yet initialized."); 58 } 59 return sInstance; 60 } 61 62 /** 63 * Create the ControllerMetricsStats singleton instance. 64 * 65 * @param context The Context for the ControllerMetricsStats. 66 * @return The singleton instance of ControllerMetricsStats. 67 */ make(@onNull Context context)68 public static ControllerMetricsStats make(@NonNull Context context) { 69 if (sInstance == null) { 70 sInstance = new ControllerMetricsStats(context); 71 } 72 return sInstance; 73 } 74 75 /** 76 * Create the ControllerMetricsStats singleton instance, testing purpose only. 77 * 78 * @param context The Context for the ControllerMetricsStats. 79 * @param satelliteStats SatelliteStats instance to test 80 * @return The singleton instance of ControllerMetricsStats. 81 */ 82 @VisibleForTesting make(@onNull Context context, @NonNull SatelliteStats satelliteStats)83 public static ControllerMetricsStats make(@NonNull Context context, 84 @NonNull SatelliteStats satelliteStats) { 85 if (sInstance == null) { 86 sInstance = new ControllerMetricsStats(context, satelliteStats); 87 } 88 return sInstance; 89 } 90 91 /** 92 * Create the ControllerMetricsStats to manage metrics report for 93 * {@link SatelliteStats.SatelliteControllerParams} 94 * @param context The Context for the ControllerMetricsStats. 95 */ ControllerMetricsStats(@onNull Context context)96 ControllerMetricsStats(@NonNull Context context) { 97 mContext = context; 98 mSatelliteStats = SatelliteStats.getInstance(); 99 } 100 101 /** 102 * Create the ControllerMetricsStats to manage metrics report for 103 * {@link SatelliteStats.SatelliteControllerParams} 104 * 105 * @param context The Context for the ControllerMetricsStats. 106 * @param satelliteStats SatelliteStats object used for testing purpose 107 */ 108 @VisibleForTesting ControllerMetricsStats(@onNull Context context, @NonNull SatelliteStats satelliteStats)109 protected ControllerMetricsStats(@NonNull Context context, 110 @NonNull SatelliteStats satelliteStats) { 111 mContext = context; 112 mSatelliteStats = satelliteStats; 113 } 114 115 /** Report a counter when an attempt for satellite service on is successfully done */ reportServiceEnablementSuccessCount()116 public void reportServiceEnablementSuccessCount() { 117 logd("reportServiceEnablementSuccessCount()"); 118 mSatelliteStats.onSatelliteControllerMetrics( 119 new SatelliteStats.SatelliteControllerParams.Builder() 120 .setCountOfSatelliteServiceEnablementsSuccess(ADD_COUNT) 121 .build()); 122 } 123 124 /** Report a counter when an attempt for satellite service on is failed */ reportServiceEnablementFailCount()125 public void reportServiceEnablementFailCount() { 126 logd("reportServiceEnablementFailCount()"); 127 mSatelliteStats.onSatelliteControllerMetrics( 128 new SatelliteStats.SatelliteControllerParams.Builder() 129 .setCountOfSatelliteServiceEnablementsFail(ADD_COUNT) 130 .build()); 131 } 132 133 /** Report a counter when an attempt for outgoing datagram is successfully done */ reportOutgoingDatagramSuccessCount( @onNull @atelliteManager.DatagramType int datagramType, boolean isDemoMode)134 public void reportOutgoingDatagramSuccessCount( 135 @NonNull @SatelliteManager.DatagramType int datagramType, boolean isDemoMode) { 136 SatelliteStats.SatelliteControllerParams.Builder builder = 137 new SatelliteStats.SatelliteControllerParams.Builder(); 138 139 if (isDemoMode) { 140 builder.setCountOfDemoModeOutgoingDatagramSuccess(ADD_COUNT); 141 } else { 142 builder.setCountOfOutgoingDatagramSuccess(ADD_COUNT); 143 if (SatelliteServiceUtils.isSosMessage(datagramType)) { 144 builder.setCountOfDatagramTypeSosSmsSuccess(ADD_COUNT); 145 } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_LOCATION_SHARING) { 146 builder.setCountOfDatagramTypeLocationSharingSuccess(ADD_COUNT); 147 } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_KEEP_ALIVE) { 148 builder.setCountOfDatagramTypeKeepAliveSuccess(ADD_COUNT).build(); 149 } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_SMS) { 150 builder.setCountOfOutgoingDatagramTypeSmsSuccess(ADD_COUNT); 151 } 152 } 153 154 SatelliteStats.SatelliteControllerParams controllerParam = builder.build(); 155 logd("reportServiceEnablementSuccessCount(): " + controllerParam); 156 mSatelliteStats.onSatelliteControllerMetrics(controllerParam); 157 } 158 159 /** Report a counter when an attempt for outgoing datagram is failed */ reportOutgoingDatagramFailCount( @onNull @atelliteManager.DatagramType int datagramType, boolean isDemoMode)160 public void reportOutgoingDatagramFailCount( 161 @NonNull @SatelliteManager.DatagramType int datagramType, boolean isDemoMode) { 162 SatelliteStats.SatelliteControllerParams.Builder builder = 163 new SatelliteStats.SatelliteControllerParams.Builder(); 164 165 if (isDemoMode) { 166 builder.setCountOfDemoModeOutgoingDatagramFail(ADD_COUNT); 167 } else { 168 builder.setCountOfOutgoingDatagramFail(ADD_COUNT); 169 if (SatelliteServiceUtils.isSosMessage(datagramType)) { 170 builder.setCountOfDatagramTypeSosSmsFail(ADD_COUNT); 171 } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_LOCATION_SHARING) { 172 builder.setCountOfDatagramTypeLocationSharingFail(ADD_COUNT); 173 } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_KEEP_ALIVE) { 174 builder.setCountOfDatagramTypeKeepAliveFail(ADD_COUNT); 175 } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_SMS) { 176 builder.setCountOfOutgoingDatagramTypeSmsFail(ADD_COUNT); 177 } 178 } 179 180 SatelliteStats.SatelliteControllerParams controllerParam = builder.build(); 181 logd("reportOutgoingDatagramFailCount(): " + controllerParam); 182 mSatelliteStats.onSatelliteControllerMetrics(controllerParam); 183 } 184 185 /** Increase counters for successful and failed incoming datagram attempts */ reportIncomingDatagramCount( @onNull @atelliteManager.SatelliteResult int result, boolean isDemoMode)186 public void reportIncomingDatagramCount( 187 @NonNull @SatelliteManager.SatelliteResult int result, boolean isDemoMode) { 188 SatelliteStats.SatelliteControllerParams.Builder builder = 189 new SatelliteStats.SatelliteControllerParams.Builder(); 190 if (isDemoMode) { 191 if (result == SatelliteManager.SATELLITE_RESULT_SUCCESS) { 192 builder.setCountOfDemoModeIncomingDatagramSuccess(ADD_COUNT); 193 } else { 194 builder.setCountOfDemoModeIncomingDatagramFail(ADD_COUNT); 195 } 196 } else { 197 if (result == SatelliteManager.SATELLITE_RESULT_SUCCESS) { 198 builder.setCountOfIncomingDatagramSuccess(ADD_COUNT) 199 .setCountOfIncomingDatagramTypeSosSmsSuccess(ADD_COUNT); 200 201 } else { 202 builder.setCountOfIncomingDatagramFail(ADD_COUNT) 203 .setCountOfIncomingDatagramTypeSosSmsFail(ADD_COUNT); 204 } 205 } 206 SatelliteStats.SatelliteControllerParams controllerParam = builder.build(); 207 logd("reportIncomingDatagramCount(): " + controllerParam); 208 mSatelliteStats.onSatelliteControllerMetrics(controllerParam); 209 } 210 211 /** Increase counters for successful and failed incoming ntn sms attempts */ reportIncomingNtnSmsCount( @onNull @atelliteManager.SatelliteResult int result)212 public void reportIncomingNtnSmsCount( 213 @NonNull @SatelliteManager.SatelliteResult int result) { 214 SatelliteStats.SatelliteControllerParams.Builder builder = 215 new SatelliteStats.SatelliteControllerParams.Builder(); 216 if (result == SatelliteManager.SATELLITE_RESULT_SUCCESS) { 217 builder.setCountOfIncomingDatagramTypeSmsSuccess(ADD_COUNT); 218 } else { 219 builder.setCountOfIncomingDatagramTypeSmsFail(ADD_COUNT); 220 } 221 SatelliteStats.SatelliteControllerParams controllerParam = builder.build(); 222 logd("reportIncomingNtnSmsCount(): " + controllerParam); 223 mSatelliteStats.onSatelliteControllerMetrics(controllerParam); 224 } 225 226 /** Report a counter when an attempt for de-provision is success or not */ reportProvisionCount(@onNull @atelliteManager.SatelliteResult int result)227 public void reportProvisionCount(@NonNull @SatelliteManager.SatelliteResult int result) { 228 SatelliteStats.SatelliteControllerParams controllerParam; 229 if (result == SatelliteManager.SATELLITE_RESULT_SUCCESS) { 230 controllerParam = new SatelliteStats.SatelliteControllerParams.Builder() 231 .setCountOfProvisionSuccess(ADD_COUNT) 232 .build(); 233 } else { 234 controllerParam = new SatelliteStats.SatelliteControllerParams.Builder() 235 .setCountOfProvisionFail(ADD_COUNT) 236 .build(); 237 } 238 logd("reportProvisionCount(): " + controllerParam); 239 mSatelliteStats.onSatelliteControllerMetrics(controllerParam); 240 } 241 242 /** Report a counter when an attempt for de-provision is success or not */ reportDeprovisionCount(@onNull @atelliteManager.SatelliteResult int result)243 public void reportDeprovisionCount(@NonNull @SatelliteManager.SatelliteResult int result) { 244 SatelliteStats.SatelliteControllerParams controllerParam; 245 if (result == SatelliteManager.SATELLITE_RESULT_SUCCESS) { 246 controllerParam = new SatelliteStats.SatelliteControllerParams.Builder() 247 .setCountOfDeprovisionSuccess(ADD_COUNT) 248 .build(); 249 } else { 250 controllerParam = new SatelliteStats.SatelliteControllerParams.Builder() 251 .setCountOfDeprovisionFail(ADD_COUNT) 252 .build(); 253 } 254 logd("reportDeprovisionCount(): " + controllerParam); 255 mSatelliteStats.onSatelliteControllerMetrics(controllerParam); 256 } 257 258 /** 259 * Report a counter when checking result whether satellite communication is allowed or not for 260 * current location. 261 */ reportAllowedSatelliteAccessCount(boolean isAllowed)262 public void reportAllowedSatelliteAccessCount(boolean isAllowed) { 263 SatelliteStats.SatelliteControllerParams.Builder builder; 264 if (isAllowed) { 265 builder = new SatelliteStats.SatelliteControllerParams.Builder() 266 .setCountOfAllowedSatelliteAccess(ADD_COUNT); 267 } else { 268 builder = new SatelliteStats.SatelliteControllerParams.Builder() 269 .setCountOfDisallowedSatelliteAccess(ADD_COUNT); 270 } 271 SatelliteStats.SatelliteControllerParams controllerParam = builder.build(); 272 logd("reportAllowedSatelliteAccessCount:" + controllerParam); 273 mSatelliteStats.onSatelliteControllerMetrics(controllerParam); 274 } 275 276 /** 277 * Report a counter when checking whether satellite communication for current location is 278 * allowed has failed. 279 */ reportFailedSatelliteAccessCheckCount()280 public void reportFailedSatelliteAccessCheckCount() { 281 SatelliteStats.SatelliteControllerParams controllerParam = 282 new SatelliteStats.SatelliteControllerParams.Builder() 283 .setCountOfSatelliteAccessCheckFail(ADD_COUNT).build(); 284 logd("reportFailedSatelliteAccessCheckCount:" + controllerParam); 285 mSatelliteStats.onSatelliteControllerMetrics(controllerParam); 286 } 287 288 /** Return the total service up time for satellite service */ 289 @VisibleForTesting captureTotalServiceUpTimeSec()290 public int captureTotalServiceUpTimeSec() { 291 long totalTimeMillis = getElapsedRealtime() - mSatelliteOnTimeMillis; 292 mSatelliteOnTimeMillis = 0; 293 return (int) (totalTimeMillis / 1000); 294 } 295 296 /** Return the total battery charge time while satellite service is on */ 297 @VisibleForTesting captureTotalBatteryChargeTimeSec()298 public int captureTotalBatteryChargeTimeSec() { 299 int totalTime = mTotalBatteryChargeTimeSec; 300 mTotalBatteryChargeTimeSec = 0; 301 return totalTime; 302 } 303 304 /** Capture the satellite service on time and register battery monitor */ onSatelliteEnabled()305 public void onSatelliteEnabled() { 306 if (!isSatelliteModemOn()) { 307 mIsSatelliteModemOn = true; 308 309 startCaptureBatteryLevel(); 310 311 // log the timestamp of the satellite modem power on 312 mSatelliteOnTimeMillis = getElapsedRealtime(); 313 314 // register broadcast receiver for monitoring battery status change 315 IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); 316 317 logd("register BatteryStatusReceiver"); 318 mContext.registerReceiver(mBatteryStatusReceiver, filter); 319 } 320 } 321 322 /** Capture the satellite service off time and de-register battery monitor */ onSatelliteDisabled()323 public void onSatelliteDisabled() { 324 if (isSatelliteModemOn()) { 325 mIsSatelliteModemOn = false; 326 327 logd("unregister BatteryStatusReceiver"); 328 mContext.unregisterReceiver(mBatteryStatusReceiver); 329 330 int totalServiceUpTime = captureTotalServiceUpTimeSec(); 331 int batteryConsumptionPercent = captureTotalBatteryConsumptionPercent(mContext); 332 int totalBatteryChargeTime = captureTotalBatteryChargeTimeSec(); 333 334 // report metrics about service up time and battery 335 SatelliteStats.SatelliteControllerParams controllerParam = 336 new SatelliteStats.SatelliteControllerParams.Builder() 337 .setTotalServiceUptimeSec(totalServiceUpTime) 338 .setTotalBatteryConsumptionPercent(batteryConsumptionPercent) 339 .setTotalBatteryChargedTimeSec(totalBatteryChargeTime) 340 .build(); 341 logd("onSatelliteDisabled(): " + controllerParam); 342 mSatelliteStats.onSatelliteControllerMetrics(controllerParam); 343 } 344 } 345 346 /** Log the total battery charging time when satellite service is on */ updateSatelliteBatteryChargeTime(boolean isCharged)347 private void updateSatelliteBatteryChargeTime(boolean isCharged) { 348 logd("updateSatelliteBatteryChargeTime(" + isCharged + ")"); 349 // update only when the charge state has changed 350 if (mIsBatteryCharged == null || isCharged != mIsBatteryCharged) { 351 mIsBatteryCharged = isCharged; 352 353 // When charged, log the start time of battery charging 354 if (isCharged) { 355 mBatteryChargedStartTime = getElapsedRealtime(); 356 // When discharged, log the accumulated total battery charging time. 357 } else { 358 mTotalBatteryChargeTimeSec += 359 (int) ((getElapsedRealtime() - mBatteryChargedStartTime) / 1000); 360 mBatteryChargedStartTime = 0; 361 } 362 } 363 } 364 365 /** Capture the battery level when satellite service is on */ 366 @VisibleForTesting startCaptureBatteryLevel()367 public void startCaptureBatteryLevel() { 368 try { 369 BatteryManager batteryManager = mContext.getSystemService(BatteryManager.class); 370 mBatteryLevelWhenServiceOn = 371 batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY); 372 logd("sBatteryLevelWhenServiceOn = " + mBatteryLevelWhenServiceOn); 373 } catch (NullPointerException e) { 374 loge("BatteryManager is null"); 375 } 376 } 377 378 /** Capture the total consumption level when service is off */ 379 @VisibleForTesting captureTotalBatteryConsumptionPercent(Context context)380 public int captureTotalBatteryConsumptionPercent(Context context) { 381 try { 382 BatteryManager batteryManager = context.getSystemService(BatteryManager.class); 383 int currentLevel = 384 batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY); 385 return Math.max((mBatteryLevelWhenServiceOn - currentLevel), 0); 386 } catch (NullPointerException e) { 387 loge("BatteryManager is null"); 388 return 0; 389 } 390 } 391 392 /** Capture the latest provisioned state for satellite service */ 393 @VisibleForTesting setIsProvisioned(boolean isProvisioned)394 public void setIsProvisioned(boolean isProvisioned) { 395 logd("setIsProvisioned:" + isProvisioned); 396 mSatelliteStats.onSatelliteControllerMetrics( 397 new SatelliteStats.SatelliteControllerParams.Builder() 398 .setIsProvisioned(isProvisioned) 399 .build()); 400 } 401 402 /** Capture the NB-IoT NTN carrier ID */ setCarrierId(int carrierId)403 public void setCarrierId(int carrierId) { 404 logd("setCarrierId:" + carrierId); 405 mSatelliteStats.onSatelliteControllerMetrics( 406 new SatelliteStats.SatelliteControllerParams.Builder() 407 .setCarrierId(carrierId) 408 .build()); 409 } 410 411 /** 412 * Report a counter when allowed state has changed. 413 */ reportAllowedStateChanged()414 public void reportAllowedStateChanged() { 415 logd("reportAllowedStateChanged:"); 416 mSatelliteStats.onSatelliteControllerMetrics( 417 new SatelliteStats.SatelliteControllerParams.Builder() 418 .setCountOfSatelliteAllowedStateChangedEvents(ADD_COUNT) 419 .build()); 420 } 421 422 /** 423 * Report a counter when location query was successful or failed. 424 */ reportLocationQuerySuccessful(boolean result)425 public void reportLocationQuerySuccessful(boolean result) { 426 SatelliteStats.SatelliteControllerParams.Builder builder; 427 if (result) { 428 builder = new SatelliteStats.SatelliteControllerParams.Builder() 429 .setCountOfSuccessfulLocationQueries(ADD_COUNT); 430 } else { 431 builder = new SatelliteStats.SatelliteControllerParams.Builder() 432 .setCountOfFailedLocationQueries(ADD_COUNT); 433 } 434 SatelliteStats.SatelliteControllerParams controllerParam = builder.build(); 435 logd("reportLocationQuerySuccessful:" + controllerParam); 436 mSatelliteStats.onSatelliteControllerMetrics(controllerParam); 437 } 438 439 /** 440 * Report a current version of satellite access config. 441 */ reportCurrentVersionOfSatelliteAccessConfig(int version)442 public void reportCurrentVersionOfSatelliteAccessConfig(int version) { 443 logd("reportCurrentVersionOfSatelliteAccessConfig:" + version); 444 mSatelliteStats.onSatelliteControllerMetrics( 445 new SatelliteStats.SatelliteControllerParams.Builder() 446 .setVersionOfSatelliteAccessControl(version) 447 .build()); 448 } 449 450 /** 451 * Add count when the notification for P2P SMS over satellite avaibility is shown or removed. 452 */ reportP2PSmsEligibilityNotificationsCount(boolean isEligible)453 public void reportP2PSmsEligibilityNotificationsCount(boolean isEligible) { 454 SatelliteStats.SatelliteControllerParams.Builder builder; 455 if (isEligible) { 456 builder = new SatelliteStats.SatelliteControllerParams.Builder() 457 .setCountOfP2PSmsAvailableNotificationShown(ADD_COUNT); 458 } else { 459 builder = new SatelliteStats.SatelliteControllerParams.Builder() 460 .setCountOfP2PSmsAvailableNotificationRemoved(ADD_COUNT); 461 462 } 463 SatelliteStats.SatelliteControllerParams controllerParam = builder.build(); 464 logd("reportP2PSmsEligibilityNotificationsCount:" + controllerParam); 465 mSatelliteStats.onSatelliteControllerMetrics(controllerParam); 466 } 467 468 /** Capture the latest provisioned state for satellite service */ setIsNtnOnlyCarrier(boolean isNtnOnlyCarrier)469 public void setIsNtnOnlyCarrier(boolean isNtnOnlyCarrier) { 470 logd("setIsNtnOnlyCarrier:" + isNtnOnlyCarrier); 471 mSatelliteStats.onSatelliteControllerMetrics( 472 new SatelliteStats.SatelliteControllerParams.Builder() 473 .setIsNtnOnlyCarrier(isNtnOnlyCarrier) 474 .build()); 475 } 476 477 /** Receives the battery status whether it is in charging or not, update interval is 60 sec. */ 478 private final BroadcastReceiver mBatteryStatusReceiver = new BroadcastReceiver() { 479 private long mLastUpdatedTime = 0; 480 private static final long UPDATE_INTERVAL = 60 * 1000; 481 482 @Override 483 public void onReceive(Context context, Intent intent) { 484 long elapsedTimeSinceBoot = getElapsedRealtime(); 485 if (elapsedTimeSinceBoot - mLastUpdatedTime > UPDATE_INTERVAL) { 486 mLastUpdatedTime = elapsedTimeSinceBoot; 487 int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1); 488 boolean isCharged = (status == BatteryManager.BATTERY_STATUS_CHARGING); 489 logd("Battery is charged(" + isCharged + ")"); 490 updateSatelliteBatteryChargeTime(isCharged); 491 } 492 } 493 }; 494 495 @VisibleForTesting isSatelliteModemOn()496 public boolean isSatelliteModemOn() { 497 return mIsSatelliteModemOn; 498 } 499 500 @VisibleForTesting getElapsedRealtime()501 public long getElapsedRealtime() { 502 return SystemClock.elapsedRealtime(); 503 } 504 logd(@onNull String log)505 private static void logd(@NonNull String log) { 506 Log.d(TAG, log); 507 } 508 loge(@onNull String log)509 private static void loge(@NonNull String log) { 510 Log.e(TAG, log); 511 } 512 } 513