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 31 /** 32 * Stats to log to satellite metrics 33 */ 34 public class ControllerMetricsStats { 35 private static final int ADD_COUNT = 1; 36 private static final String TAG = ControllerMetricsStats.class.getSimpleName(); 37 private static final boolean DBG = false; 38 39 private static ControllerMetricsStats sInstance; 40 41 private final Context mContext; 42 private SatelliteStats mSatelliteStats; 43 44 private long mSatelliteOnTimeMillis; 45 private int mBatteryLevelWhenServiceOn; 46 private boolean mIsSatelliteModemOn; 47 private Boolean mIsBatteryCharged = null; 48 private int mBatteryChargedStartTimeSec; 49 private int mTotalBatteryChargeTimeSec; 50 51 /** 52 * @return The singleton instance of ControllerMetricsStats. 53 */ getInstance()54 public static ControllerMetricsStats getInstance() { 55 if (sInstance == null) { 56 loge("ControllerMetricsStats was not yet initialized."); 57 } 58 return sInstance; 59 } 60 61 /** 62 * Create the ControllerMetricsStats singleton instance. 63 * 64 * @param context The Context for the ControllerMetricsStats. 65 * @return The singleton instance of ControllerMetricsStats. 66 */ make(@onNull Context context)67 public static ControllerMetricsStats make(@NonNull Context context) { 68 if (sInstance == null) { 69 sInstance = new ControllerMetricsStats(context); 70 } 71 return sInstance; 72 } 73 74 /** 75 * Create the ControllerMetricsStats singleton instance, testing purpose only. 76 * 77 * @param context The Context for the ControllerMetricsStats. 78 * @param satelliteStats SatelliteStats instance to test 79 * @return The singleton instance of ControllerMetricsStats. 80 */ 81 @VisibleForTesting make(@onNull Context context, @NonNull SatelliteStats satelliteStats)82 public static ControllerMetricsStats make(@NonNull Context context, 83 @NonNull SatelliteStats satelliteStats) { 84 if (sInstance == null) { 85 sInstance = new ControllerMetricsStats(context, satelliteStats); 86 } 87 return sInstance; 88 } 89 90 /** 91 * Create the ControllerMetricsStats to manage metrics report for 92 * {@link SatelliteStats.SatelliteControllerParams} 93 * @param context The Context for the ControllerMetricsStats. 94 */ ControllerMetricsStats(@onNull Context context)95 ControllerMetricsStats(@NonNull Context context) { 96 mContext = context; 97 mSatelliteStats = SatelliteStats.getInstance(); 98 } 99 100 /** 101 * Create the ControllerMetricsStats to manage metrics report for 102 * {@link SatelliteStats.SatelliteControllerParams} 103 * 104 * @param context The Context for the ControllerMetricsStats. 105 * @param satelliteStats SatelliteStats object used for testing purpose 106 */ 107 @VisibleForTesting ControllerMetricsStats(@onNull Context context, @NonNull SatelliteStats satelliteStats)108 protected ControllerMetricsStats(@NonNull Context context, 109 @NonNull SatelliteStats satelliteStats) { 110 mContext = context; 111 mSatelliteStats = satelliteStats; 112 } 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("reportServiceEnablementSuccessCount()"); 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)134 public void reportOutgoingDatagramSuccessCount( 135 @NonNull @SatelliteManager.DatagramType int datagramType) { 136 SatelliteStats.SatelliteControllerParams controllerParam; 137 if (datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE) { 138 controllerParam = new SatelliteStats.SatelliteControllerParams.Builder() 139 .setCountOfOutgoingDatagramSuccess(ADD_COUNT) 140 .setCountOfDatagramTypeSosSmsSuccess(ADD_COUNT) 141 .build(); 142 } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_LOCATION_SHARING) { 143 controllerParam = new SatelliteStats.SatelliteControllerParams.Builder() 144 .setCountOfOutgoingDatagramSuccess(ADD_COUNT) 145 .setCountOfDatagramTypeLocationSharingSuccess(ADD_COUNT) 146 .build(); 147 } else { // datagramType == SatelliteManager.DATAGRAM_TYPE_UNKNOWN 148 controllerParam = new SatelliteStats.SatelliteControllerParams.Builder() 149 .setCountOfOutgoingDatagramSuccess(ADD_COUNT) 150 .build(); 151 } 152 logd("reportServiceEnablementSuccessCount(): " + controllerParam); 153 mSatelliteStats.onSatelliteControllerMetrics(controllerParam); 154 } 155 156 /** Report a counter when an attempt for outgoing datagram is failed */ reportOutgoingDatagramFailCount( @onNull @atelliteManager.DatagramType int datagramType)157 public void reportOutgoingDatagramFailCount( 158 @NonNull @SatelliteManager.DatagramType int datagramType) { 159 SatelliteStats.SatelliteControllerParams controllerParam; 160 if (datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE) { 161 controllerParam = new SatelliteStats.SatelliteControllerParams.Builder() 162 .setCountOfOutgoingDatagramFail(ADD_COUNT) 163 .setCountOfDatagramTypeSosSmsFail(ADD_COUNT) 164 .build(); 165 } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_LOCATION_SHARING) { 166 controllerParam = new SatelliteStats.SatelliteControllerParams.Builder() 167 .setCountOfOutgoingDatagramFail(ADD_COUNT) 168 .setCountOfDatagramTypeLocationSharingFail(ADD_COUNT) 169 .build(); 170 } else { // datagramType == SatelliteManager.DATAGRAM_TYPE_UNKNOWN 171 controllerParam = new SatelliteStats.SatelliteControllerParams.Builder() 172 .setCountOfOutgoingDatagramFail(ADD_COUNT) 173 .build(); 174 } 175 logd("reportOutgoingDatagramFailCount(): " + controllerParam); 176 mSatelliteStats.onSatelliteControllerMetrics(controllerParam); 177 } 178 179 /** Report a counter when an attempt for incoming datagram is failed */ reportIncomingDatagramCount( @onNull @atelliteManager.SatelliteError int result)180 public void reportIncomingDatagramCount( 181 @NonNull @SatelliteManager.SatelliteError int result) { 182 SatelliteStats.SatelliteControllerParams controllerParam; 183 if (result == SatelliteManager.SATELLITE_ERROR_NONE) { 184 controllerParam = new SatelliteStats.SatelliteControllerParams.Builder() 185 .setCountOfIncomingDatagramSuccess(ADD_COUNT) 186 .build(); 187 } else { 188 controllerParam = new SatelliteStats.SatelliteControllerParams.Builder() 189 .setCountOfIncomingDatagramFail(ADD_COUNT) 190 .build(); 191 } 192 logd("reportIncomingDatagramCount(): " + controllerParam); 193 mSatelliteStats.onSatelliteControllerMetrics(controllerParam); 194 } 195 196 /** Report a counter when an attempt for de-provision is success or not */ reportProvisionCount(@onNull @atelliteManager.SatelliteError int result)197 public void reportProvisionCount(@NonNull @SatelliteManager.SatelliteError int result) { 198 SatelliteStats.SatelliteControllerParams controllerParam; 199 if (result == SatelliteManager.SATELLITE_ERROR_NONE) { 200 controllerParam = new SatelliteStats.SatelliteControllerParams.Builder() 201 .setCountOfProvisionSuccess(ADD_COUNT) 202 .build(); 203 } else { 204 controllerParam = new SatelliteStats.SatelliteControllerParams.Builder() 205 .setCountOfProvisionFail(ADD_COUNT) 206 .build(); 207 } 208 logd("reportProvisionCount(): " + controllerParam); 209 mSatelliteStats.onSatelliteControllerMetrics(controllerParam); 210 } 211 212 /** Report a counter when an attempt for de-provision is success or not */ reportDeprovisionCount(@onNull @atelliteManager.SatelliteError int result)213 public void reportDeprovisionCount(@NonNull @SatelliteManager.SatelliteError int result) { 214 SatelliteStats.SatelliteControllerParams controllerParam; 215 if (result == SatelliteManager.SATELLITE_ERROR_NONE) { 216 controllerParam = new SatelliteStats.SatelliteControllerParams.Builder() 217 .setCountOfDeprovisionSuccess(ADD_COUNT) 218 .build(); 219 } else { 220 controllerParam = new SatelliteStats.SatelliteControllerParams.Builder() 221 .setCountOfDeprovisionFail(ADD_COUNT) 222 .build(); 223 } 224 logd("reportDeprovisionCount(): " + controllerParam); 225 mSatelliteStats.onSatelliteControllerMetrics(controllerParam); 226 } 227 228 /** Return the total service up time for satellite service */ 229 @VisibleForTesting captureTotalServiceUpTimeSec()230 public int captureTotalServiceUpTimeSec() { 231 long totalTimeMillis = getCurrentTime() - mSatelliteOnTimeMillis; 232 mSatelliteOnTimeMillis = 0; 233 return (int) (totalTimeMillis / 1000); 234 } 235 236 /** Return the total battery charge time while satellite service is on */ 237 @VisibleForTesting captureTotalBatteryChargeTimeSec()238 public int captureTotalBatteryChargeTimeSec() { 239 int totalTime = mTotalBatteryChargeTimeSec; 240 mTotalBatteryChargeTimeSec = 0; 241 return totalTime; 242 } 243 244 /** Capture the satellite service on time and register battery monitor */ onSatelliteEnabled()245 public void onSatelliteEnabled() { 246 if (!isSatelliteModemOn()) { 247 mIsSatelliteModemOn = true; 248 249 startCaptureBatteryLevel(); 250 251 // log the timestamp of the satellite modem power on 252 mSatelliteOnTimeMillis = getCurrentTime(); 253 254 // register broadcast receiver for monitoring battery status change 255 IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); 256 257 logd("register BatteryStatusReceiver"); 258 mContext.registerReceiver(mBatteryStatusReceiver, filter); 259 } 260 } 261 262 /** Capture the satellite service off time and de-register battery monitor */ onSatelliteDisabled()263 public void onSatelliteDisabled() { 264 if (isSatelliteModemOn()) { 265 mIsSatelliteModemOn = false; 266 267 logd("unregister BatteryStatusReceiver"); 268 mContext.unregisterReceiver(mBatteryStatusReceiver); 269 270 int totalServiceUpTime = captureTotalServiceUpTimeSec(); 271 int batteryConsumptionPercent = captureTotalBatteryConsumptionPercent(mContext); 272 int totalBatteryChargeTime = captureTotalBatteryChargeTimeSec(); 273 274 // report metrics about service up time and battery 275 SatelliteStats.SatelliteControllerParams controllerParam = 276 new SatelliteStats.SatelliteControllerParams.Builder() 277 .setTotalServiceUptimeSec(totalServiceUpTime) 278 .setTotalBatteryConsumptionPercent(batteryConsumptionPercent) 279 .setTotalBatteryChargedTimeSec(totalBatteryChargeTime) 280 .build(); 281 logd("onSatelliteDisabled(): " + controllerParam); 282 mSatelliteStats.onSatelliteControllerMetrics(controllerParam); 283 } 284 } 285 286 /** Log the total battery charging time when satellite service is on */ updateSatelliteBatteryChargeTime(boolean isCharged)287 private void updateSatelliteBatteryChargeTime(boolean isCharged) { 288 logd("updateSatelliteBatteryChargeTime(" + isCharged + ")"); 289 // update only when the charge state has changed 290 if (mIsBatteryCharged == null || isCharged != mIsBatteryCharged) { 291 mIsBatteryCharged = isCharged; 292 293 // When charged, log the start time of battery charging 294 if (isCharged) { 295 mBatteryChargedStartTimeSec = (int) (getCurrentTime() / 1000); 296 // When discharged, log the accumulated total battery charging time. 297 } else { 298 mTotalBatteryChargeTimeSec += 299 (int) (getCurrentTime() / 1000) 300 - mBatteryChargedStartTimeSec; 301 mBatteryChargedStartTimeSec = 0; 302 } 303 } 304 } 305 306 /** Capture the battery level when satellite service is on */ 307 @VisibleForTesting startCaptureBatteryLevel()308 public void startCaptureBatteryLevel() { 309 try { 310 BatteryManager batteryManager = mContext.getSystemService(BatteryManager.class); 311 mBatteryLevelWhenServiceOn = 312 batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY); 313 logd("sBatteryLevelWhenServiceOn = " + mBatteryLevelWhenServiceOn); 314 } catch (NullPointerException e) { 315 loge("BatteryManager is null"); 316 } 317 } 318 319 /** Capture the total consumption level when service is off */ 320 @VisibleForTesting captureTotalBatteryConsumptionPercent(Context context)321 public int captureTotalBatteryConsumptionPercent(Context context) { 322 try { 323 BatteryManager batteryManager = context.getSystemService(BatteryManager.class); 324 int currentLevel = 325 batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY); 326 return Math.max((mBatteryLevelWhenServiceOn - currentLevel), 0); 327 } catch (NullPointerException e) { 328 loge("BatteryManager is null"); 329 return 0; 330 } 331 } 332 333 /** Receives the battery status whether it is in charging or not, update interval is 60 sec. */ 334 private final BroadcastReceiver mBatteryStatusReceiver = new BroadcastReceiver() { 335 private long mLastUpdatedTime = 0; 336 private static final long UPDATE_INTERVAL = 60 * 1000; 337 338 @Override 339 public void onReceive(Context context, Intent intent) { 340 long currentTime = getCurrentTime(); 341 if (currentTime - mLastUpdatedTime > UPDATE_INTERVAL) { 342 mLastUpdatedTime = currentTime; 343 int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1); 344 boolean isCharged = (status == BatteryManager.BATTERY_STATUS_CHARGING); 345 logd("Battery is charged(" + isCharged + ")"); 346 updateSatelliteBatteryChargeTime(isCharged); 347 } 348 } 349 }; 350 351 @VisibleForTesting isSatelliteModemOn()352 public boolean isSatelliteModemOn() { 353 return mIsSatelliteModemOn; 354 } 355 356 @VisibleForTesting getCurrentTime()357 public long getCurrentTime() { 358 return System.currentTimeMillis(); 359 } 360 logd(@onNull String log)361 private static void logd(@NonNull String log) { 362 if (DBG) { 363 Log.d(TAG, log); 364 } 365 } 366 loge(@onNull String log)367 private static void loge(@NonNull String log) { 368 Log.e(TAG, log); 369 } 370 } 371