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.telephony.satellite.SatelliteManager; 26 import android.util.Log; 27 28 import com.android.internal.annotations.VisibleForTesting; 29 import com.android.internal.telephony.metrics.SatelliteStats; 30 import com.android.internal.telephony.satellite.SatelliteServiceUtils; 31 32 /** 33 * Stats to log to satellite metrics 34 */ 35 public class ControllerMetricsStats { 36 private static final int ADD_COUNT = 1; 37 private static final String TAG = ControllerMetricsStats.class.getSimpleName(); 38 private static final boolean DBG = false; 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 int mBatteryChargedStartTimeSec; 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 116 /** Report a counter when an attempt for satellite service on is successfully done */ reportServiceEnablementSuccessCount()117 public void reportServiceEnablementSuccessCount() { 118 logd("reportServiceEnablementSuccessCount()"); 119 mSatelliteStats.onSatelliteControllerMetrics( 120 new SatelliteStats.SatelliteControllerParams.Builder() 121 .setCountOfSatelliteServiceEnablementsSuccess(ADD_COUNT) 122 .build()); 123 } 124 125 /** Report a counter when an attempt for satellite service on is failed */ reportServiceEnablementFailCount()126 public void reportServiceEnablementFailCount() { 127 logd("reportServiceEnablementSuccessCount()"); 128 mSatelliteStats.onSatelliteControllerMetrics( 129 new SatelliteStats.SatelliteControllerParams.Builder() 130 .setCountOfSatelliteServiceEnablementsFail(ADD_COUNT) 131 .build()); 132 } 133 134 /** Report a counter when an attempt for outgoing datagram is successfully done */ reportOutgoingDatagramSuccessCount( @onNull @atelliteManager.DatagramType int datagramType, boolean isDemoMode)135 public void reportOutgoingDatagramSuccessCount( 136 @NonNull @SatelliteManager.DatagramType int datagramType, boolean isDemoMode) { 137 SatelliteStats.SatelliteControllerParams.Builder builder = 138 new SatelliteStats.SatelliteControllerParams.Builder(); 139 140 if (isDemoMode) { 141 builder.setCountOfDemoModeOutgoingDatagramSuccess(ADD_COUNT); 142 } else { 143 builder.setCountOfOutgoingDatagramSuccess(ADD_COUNT); 144 } 145 146 if (SatelliteServiceUtils.isSosMessage(datagramType)) { 147 builder.setCountOfDatagramTypeSosSmsSuccess(ADD_COUNT); 148 } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_LOCATION_SHARING) { 149 builder.setCountOfDatagramTypeLocationSharingSuccess(ADD_COUNT); 150 } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_KEEP_ALIVE) { 151 builder.setCountOfDatagramTypeKeepAliveSuccess(ADD_COUNT).build(); 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 } 170 171 if (SatelliteServiceUtils.isSosMessage(datagramType)) { 172 builder.setCountOfDatagramTypeSosSmsFail(ADD_COUNT); 173 } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_LOCATION_SHARING) { 174 builder.setCountOfDatagramTypeLocationSharingFail(ADD_COUNT); 175 } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_KEEP_ALIVE) { 176 builder.setCountOfDatagramTypeKeepAliveFail(ADD_COUNT); 177 } 178 179 SatelliteStats.SatelliteControllerParams controllerParam = builder.build(); 180 logd("reportOutgoingDatagramFailCount(): " + controllerParam); 181 mSatelliteStats.onSatelliteControllerMetrics(controllerParam); 182 } 183 184 /** Report a counter when an attempt for incoming datagram is failed */ reportIncomingDatagramCount( @onNull @atelliteManager.SatelliteResult int result, boolean isDemoMode)185 public void reportIncomingDatagramCount( 186 @NonNull @SatelliteManager.SatelliteResult int result, boolean isDemoMode) { 187 SatelliteStats.SatelliteControllerParams.Builder builder = 188 new SatelliteStats.SatelliteControllerParams.Builder(); 189 if (isDemoMode) { 190 if (result == SatelliteManager.SATELLITE_RESULT_SUCCESS) { 191 builder.setCountOfDemoModeIncomingDatagramSuccess(ADD_COUNT); 192 } else { 193 builder.setCountOfDemoModeIncomingDatagramFail(ADD_COUNT); 194 } 195 } else { 196 if (result == SatelliteManager.SATELLITE_RESULT_SUCCESS) { 197 builder.setCountOfIncomingDatagramSuccess(ADD_COUNT); 198 } else { 199 builder.setCountOfIncomingDatagramFail(ADD_COUNT); 200 } 201 } 202 SatelliteStats.SatelliteControllerParams controllerParam = builder.build(); 203 logd("reportIncomingDatagramCount(): " + controllerParam); 204 mSatelliteStats.onSatelliteControllerMetrics(controllerParam); 205 } 206 207 /** Report a counter when an attempt for de-provision is success or not */ reportProvisionCount(@onNull @atelliteManager.SatelliteResult int result)208 public void reportProvisionCount(@NonNull @SatelliteManager.SatelliteResult int result) { 209 SatelliteStats.SatelliteControllerParams controllerParam; 210 if (result == SatelliteManager.SATELLITE_RESULT_SUCCESS) { 211 controllerParam = new SatelliteStats.SatelliteControllerParams.Builder() 212 .setCountOfProvisionSuccess(ADD_COUNT) 213 .build(); 214 } else { 215 controllerParam = new SatelliteStats.SatelliteControllerParams.Builder() 216 .setCountOfProvisionFail(ADD_COUNT) 217 .build(); 218 } 219 logd("reportProvisionCount(): " + controllerParam); 220 mSatelliteStats.onSatelliteControllerMetrics(controllerParam); 221 } 222 223 /** Report a counter when an attempt for de-provision is success or not */ reportDeprovisionCount(@onNull @atelliteManager.SatelliteResult int result)224 public void reportDeprovisionCount(@NonNull @SatelliteManager.SatelliteResult int result) { 225 SatelliteStats.SatelliteControllerParams controllerParam; 226 if (result == SatelliteManager.SATELLITE_RESULT_SUCCESS) { 227 controllerParam = new SatelliteStats.SatelliteControllerParams.Builder() 228 .setCountOfDeprovisionSuccess(ADD_COUNT) 229 .build(); 230 } else { 231 controllerParam = new SatelliteStats.SatelliteControllerParams.Builder() 232 .setCountOfDeprovisionFail(ADD_COUNT) 233 .build(); 234 } 235 logd("reportDeprovisionCount(): " + controllerParam); 236 mSatelliteStats.onSatelliteControllerMetrics(controllerParam); 237 } 238 239 /** 240 * Report a counter when checking result whether satellite communication is allowed or not for 241 * current location. 242 */ reportAllowedSatelliteAccessCount(boolean isAllowed)243 public void reportAllowedSatelliteAccessCount(boolean isAllowed) { 244 SatelliteStats.SatelliteControllerParams.Builder builder; 245 if (isAllowed) { 246 builder = new SatelliteStats.SatelliteControllerParams.Builder() 247 .setCountOfAllowedSatelliteAccess(ADD_COUNT); 248 } else { 249 builder = new SatelliteStats.SatelliteControllerParams.Builder() 250 .setCountOfDisallowedSatelliteAccess(ADD_COUNT); 251 } 252 SatelliteStats.SatelliteControllerParams controllerParam = builder.build(); 253 logd("reportAllowedSatelliteAccessCount:" + controllerParam); 254 mSatelliteStats.onSatelliteControllerMetrics(controllerParam); 255 } 256 257 /** 258 * Report a counter when checking whether satellite communication for current location is 259 * allowed has failed. 260 */ reportFailedSatelliteAccessCheckCount()261 public void reportFailedSatelliteAccessCheckCount() { 262 SatelliteStats.SatelliteControllerParams controllerParam = 263 new SatelliteStats.SatelliteControllerParams.Builder() 264 .setCountOfSatelliteAccessCheckFail(ADD_COUNT).build(); 265 logd("reportFailedSatelliteAccessCheckCount:" + controllerParam); 266 mSatelliteStats.onSatelliteControllerMetrics(controllerParam); 267 } 268 269 /** Return the total service up time for satellite service */ 270 @VisibleForTesting captureTotalServiceUpTimeSec()271 public int captureTotalServiceUpTimeSec() { 272 long totalTimeMillis = getCurrentTime() - mSatelliteOnTimeMillis; 273 mSatelliteOnTimeMillis = 0; 274 return (int) (totalTimeMillis / 1000); 275 } 276 277 /** Return the total battery charge time while satellite service is on */ 278 @VisibleForTesting captureTotalBatteryChargeTimeSec()279 public int captureTotalBatteryChargeTimeSec() { 280 int totalTime = mTotalBatteryChargeTimeSec; 281 mTotalBatteryChargeTimeSec = 0; 282 return totalTime; 283 } 284 285 /** Capture the satellite service on time and register battery monitor */ onSatelliteEnabled()286 public void onSatelliteEnabled() { 287 if (!isSatelliteModemOn()) { 288 mIsSatelliteModemOn = true; 289 290 startCaptureBatteryLevel(); 291 292 // log the timestamp of the satellite modem power on 293 mSatelliteOnTimeMillis = getCurrentTime(); 294 295 // register broadcast receiver for monitoring battery status change 296 IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); 297 298 logd("register BatteryStatusReceiver"); 299 mContext.registerReceiver(mBatteryStatusReceiver, filter); 300 } 301 } 302 303 /** Capture the satellite service off time and de-register battery monitor */ onSatelliteDisabled()304 public void onSatelliteDisabled() { 305 if (isSatelliteModemOn()) { 306 mIsSatelliteModemOn = false; 307 308 logd("unregister BatteryStatusReceiver"); 309 mContext.unregisterReceiver(mBatteryStatusReceiver); 310 311 int totalServiceUpTime = captureTotalServiceUpTimeSec(); 312 int batteryConsumptionPercent = captureTotalBatteryConsumptionPercent(mContext); 313 int totalBatteryChargeTime = captureTotalBatteryChargeTimeSec(); 314 315 // report metrics about service up time and battery 316 SatelliteStats.SatelliteControllerParams controllerParam = 317 new SatelliteStats.SatelliteControllerParams.Builder() 318 .setTotalServiceUptimeSec(totalServiceUpTime) 319 .setTotalBatteryConsumptionPercent(batteryConsumptionPercent) 320 .setTotalBatteryChargedTimeSec(totalBatteryChargeTime) 321 .build(); 322 logd("onSatelliteDisabled(): " + controllerParam); 323 mSatelliteStats.onSatelliteControllerMetrics(controllerParam); 324 } 325 } 326 327 /** Log the total battery charging time when satellite service is on */ updateSatelliteBatteryChargeTime(boolean isCharged)328 private void updateSatelliteBatteryChargeTime(boolean isCharged) { 329 logd("updateSatelliteBatteryChargeTime(" + isCharged + ")"); 330 // update only when the charge state has changed 331 if (mIsBatteryCharged == null || isCharged != mIsBatteryCharged) { 332 mIsBatteryCharged = isCharged; 333 334 // When charged, log the start time of battery charging 335 if (isCharged) { 336 mBatteryChargedStartTimeSec = (int) (getCurrentTime() / 1000); 337 // When discharged, log the accumulated total battery charging time. 338 } else { 339 mTotalBatteryChargeTimeSec += 340 (int) (getCurrentTime() / 1000) 341 - mBatteryChargedStartTimeSec; 342 mBatteryChargedStartTimeSec = 0; 343 } 344 } 345 } 346 347 /** Capture the battery level when satellite service is on */ 348 @VisibleForTesting startCaptureBatteryLevel()349 public void startCaptureBatteryLevel() { 350 try { 351 BatteryManager batteryManager = mContext.getSystemService(BatteryManager.class); 352 mBatteryLevelWhenServiceOn = 353 batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY); 354 logd("sBatteryLevelWhenServiceOn = " + mBatteryLevelWhenServiceOn); 355 } catch (NullPointerException e) { 356 loge("BatteryManager is null"); 357 } 358 } 359 360 /** Capture the total consumption level when service is off */ 361 @VisibleForTesting captureTotalBatteryConsumptionPercent(Context context)362 public int captureTotalBatteryConsumptionPercent(Context context) { 363 try { 364 BatteryManager batteryManager = context.getSystemService(BatteryManager.class); 365 int currentLevel = 366 batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY); 367 return Math.max((mBatteryLevelWhenServiceOn - currentLevel), 0); 368 } catch (NullPointerException e) { 369 loge("BatteryManager is null"); 370 return 0; 371 } 372 } 373 374 /** Receives the battery status whether it is in charging or not, update interval is 60 sec. */ 375 private final BroadcastReceiver mBatteryStatusReceiver = new BroadcastReceiver() { 376 private long mLastUpdatedTime = 0; 377 private static final long UPDATE_INTERVAL = 60 * 1000; 378 379 @Override 380 public void onReceive(Context context, Intent intent) { 381 long currentTime = getCurrentTime(); 382 if (currentTime - mLastUpdatedTime > UPDATE_INTERVAL) { 383 mLastUpdatedTime = currentTime; 384 int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1); 385 boolean isCharged = (status == BatteryManager.BATTERY_STATUS_CHARGING); 386 logd("Battery is charged(" + isCharged + ")"); 387 updateSatelliteBatteryChargeTime(isCharged); 388 } 389 } 390 }; 391 392 @VisibleForTesting isSatelliteModemOn()393 public boolean isSatelliteModemOn() { 394 return mIsSatelliteModemOn; 395 } 396 397 @VisibleForTesting getCurrentTime()398 public long getCurrentTime() { 399 return System.currentTimeMillis(); 400 } 401 logd(@onNull String log)402 private static void logd(@NonNull String log) { 403 if (DBG) { 404 Log.d(TAG, log); 405 } 406 } 407 loge(@onNull String log)408 private static void loge(@NonNull String log) { 409 Log.e(TAG, log); 410 } 411 } 412