1 /* 2 * Copyright (C) 2016 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.location.gnssmetrics; 18 19 import android.app.StatsManager; 20 import android.content.Context; 21 import android.location.GnssStatus; 22 import android.os.SystemClock; 23 import android.os.SystemProperties; 24 import android.os.connectivity.GpsBatteryStats; 25 import android.server.location.ServerLocationProtoEnums; 26 import android.text.format.DateUtils; 27 import android.util.Base64; 28 import android.util.Log; 29 import android.util.StatsEvent; 30 import android.util.TimeUtils; 31 32 import com.android.internal.app.IBatteryStats; 33 import com.android.internal.location.nano.GnssLogsProto.GnssLog; 34 import com.android.internal.location.nano.GnssLogsProto.PowerMetrics; 35 import com.android.internal.util.ConcurrentUtils; 36 import com.android.internal.util.FrameworkStatsLog; 37 38 import java.util.ArrayList; 39 import java.util.Arrays; 40 import java.util.Collections; 41 import java.util.List; 42 43 /** 44 * GnssMetrics: Is used for logging GNSS metrics 45 * 46 * @hide 47 */ 48 public class GnssMetrics { 49 50 private static final String TAG = GnssMetrics.class.getSimpleName(); 51 52 /* Constant which indicates GPS signal quality is as yet unknown */ 53 private static final int GPS_SIGNAL_QUALITY_UNKNOWN = 54 ServerLocationProtoEnums.GPS_SIGNAL_QUALITY_UNKNOWN; // -1 55 56 /* Constant which indicates GPS signal quality is poor */ 57 private static final int GPS_SIGNAL_QUALITY_POOR = 58 ServerLocationProtoEnums.GPS_SIGNAL_QUALITY_POOR; // 0 59 60 /* Constant which indicates GPS signal quality is good */ 61 private static final int GPS_SIGNAL_QUALITY_GOOD = 62 ServerLocationProtoEnums.GPS_SIGNAL_QUALITY_GOOD; // 1 63 64 /* Number of GPS signal quality levels */ 65 public static final int NUM_GPS_SIGNAL_QUALITY_LEVELS = GPS_SIGNAL_QUALITY_GOOD + 1; 66 67 /** Default time between location fixes (in millisecs) */ 68 private static final int DEFAULT_TIME_BETWEEN_FIXES_MILLISECS = 1000; 69 70 /* The time since boot when logging started */ 71 private String mLogStartInElapsedRealTime; 72 73 /** The number of hertz in one MHz */ 74 private static final double HZ_PER_MHZ = 1e6; 75 76 /* GNSS power metrics */ 77 private GnssPowerMetrics mGnssPowerMetrics; 78 79 /** Frequency range of GPS L5, Galileo E5a, QZSS J5 frequency band */ 80 private static final double L5_CARRIER_FREQ_RANGE_LOW_HZ = 1164 * HZ_PER_MHZ; 81 private static final double L5_CARRIER_FREQ_RANGE_HIGH_HZ = 1189 * HZ_PER_MHZ; 82 83 /* A boolean array indicating whether the constellation types have been used in fix. */ 84 private boolean[] mConstellationTypes; 85 /** Location failure statistics */ 86 private Statistics mLocationFailureStatistics; 87 /** Time to first fix statistics */ 88 private Statistics mTimeToFirstFixSecStatistics; 89 /** Position accuracy statistics */ 90 private Statistics mPositionAccuracyMeterStatistics; 91 /** Top 4 average CN0 statistics */ 92 private Statistics mTopFourAverageCn0Statistics; 93 /** Top 4 average CN0 statistics L5 */ 94 private Statistics mTopFourAverageCn0StatisticsL5; 95 /** Total number of sv status messages processed */ 96 private int mNumSvStatus; 97 /** Total number of L5 sv status messages processed */ 98 private int mNumL5SvStatus; 99 /** Total number of sv status messages processed, where sv is used in fix */ 100 private int mNumSvStatusUsedInFix; 101 /** Total number of L5 sv status messages processed, where sv is used in fix */ 102 private int mNumL5SvStatusUsedInFix; 103 104 /* Statsd Logging Variables Section Start */ 105 /** Location failure reports since boot used for statsd logging */ 106 private Statistics mLocationFailureReportsStatistics; 107 /** Time to first fix milli-seconds since boot used for statsd logging */ 108 private Statistics mTimeToFirstFixMilliSReportsStatistics; 109 /** Position accuracy meters since boot used for statsd logging */ 110 private Statistics mPositionAccuracyMetersReportsStatistics; 111 /** Top 4 average CN0 (db-mHz) since boot used for statsd logging */ 112 private Statistics mTopFourAverageCn0DbmHzReportsStatistics; 113 /** Top 4 average CN0 (db-mHz) L5 since boot used for statsd logging */ 114 private Statistics mL5TopFourAverageCn0DbmHzReportsStatistics; 115 /** Total number of sv status reports processed since boot used for statsd logging */ 116 private long mSvStatusReports; 117 /** Total number of L5 sv status reports processed since boot used for statsd logging */ 118 private long mL5SvStatusReports; 119 /** Total number of sv status reports processed, where sv is used in fix since boot used for 120 * statsd logging */ 121 private long mSvStatusReportsUsedInFix; 122 /** Total number of L5 sv status reports processed, where sv is used in fix since boot used for 123 * statsd logging */ 124 private long mL5SvStatusReportsUsedInFix; 125 /** Stats manager service for reporting atoms */ 126 private StatsManager mStatsManager; 127 /* Statds Logging Variables Section End */ 128 GnssMetrics(Context context, IBatteryStats stats)129 public GnssMetrics(Context context, IBatteryStats stats) { 130 mGnssPowerMetrics = new GnssPowerMetrics(stats); 131 mLocationFailureStatistics = new Statistics(); 132 mTimeToFirstFixSecStatistics = new Statistics(); 133 mPositionAccuracyMeterStatistics = new Statistics(); 134 mTopFourAverageCn0Statistics = new Statistics(); 135 mTopFourAverageCn0StatisticsL5 = new Statistics(); 136 reset(); 137 mLocationFailureReportsStatistics = new Statistics(); 138 mTimeToFirstFixMilliSReportsStatistics = new Statistics(); 139 mPositionAccuracyMetersReportsStatistics = new Statistics(); 140 mTopFourAverageCn0DbmHzReportsStatistics = new Statistics(); 141 mL5TopFourAverageCn0DbmHzReportsStatistics = new Statistics(); 142 mStatsManager = (StatsManager) context.getSystemService(Context.STATS_MANAGER); 143 registerGnssStats(); 144 } 145 146 /** 147 * Logs the status of a location report received from the HAL 148 */ logReceivedLocationStatus(boolean isSuccessful)149 public void logReceivedLocationStatus(boolean isSuccessful) { 150 if (!isSuccessful) { 151 mLocationFailureStatistics.addItem(1.0); 152 mLocationFailureReportsStatistics.addItem(1.0); 153 return; 154 } 155 mLocationFailureStatistics.addItem(0.0); 156 mLocationFailureReportsStatistics.addItem(0.0); 157 } 158 159 /** 160 * Logs missed reports 161 */ logMissedReports(int desiredTimeBetweenFixesMilliSeconds, int actualTimeBetweenFixesMilliSeconds)162 public void logMissedReports(int desiredTimeBetweenFixesMilliSeconds, 163 int actualTimeBetweenFixesMilliSeconds) { 164 int numReportMissed = (actualTimeBetweenFixesMilliSeconds / Math.max( 165 DEFAULT_TIME_BETWEEN_FIXES_MILLISECS, desiredTimeBetweenFixesMilliSeconds)) - 1; 166 if (numReportMissed > 0) { 167 for (int i = 0; i < numReportMissed; i++) { 168 mLocationFailureStatistics.addItem(1.0); 169 mLocationFailureReportsStatistics.addItem(1.0); 170 } 171 } 172 } 173 174 /** 175 * Logs time to first fix 176 */ logTimeToFirstFixMilliSecs(int timeToFirstFixMilliSeconds)177 public void logTimeToFirstFixMilliSecs(int timeToFirstFixMilliSeconds) { 178 mTimeToFirstFixSecStatistics.addItem((double) (timeToFirstFixMilliSeconds / 1000)); 179 mTimeToFirstFixMilliSReportsStatistics.addItem(timeToFirstFixMilliSeconds); 180 } 181 182 /** 183 * Logs position accuracy 184 */ logPositionAccuracyMeters(float positionAccuracyMeters)185 public void logPositionAccuracyMeters(float positionAccuracyMeters) { 186 mPositionAccuracyMeterStatistics.addItem((double) positionAccuracyMeters); 187 mPositionAccuracyMetersReportsStatistics.addItem(positionAccuracyMeters); 188 } 189 190 /** 191 * Logs CN0 when at least 4 SVs are available 192 * 193 * @param cn0s 194 * @param numSv 195 * @param svCarrierFreqs 196 */ logCn0(float[] cn0s, int numSv, float[] svCarrierFreqs)197 public void logCn0(float[] cn0s, int numSv, float[] svCarrierFreqs) { 198 // Calculate L5 Cn0 199 logCn0L5(numSv, cn0s, svCarrierFreqs); 200 if (numSv == 0 || cn0s == null || cn0s.length == 0 || cn0s.length < numSv) { 201 if (numSv == 0) { 202 mGnssPowerMetrics.reportSignalQuality(null, 0); 203 } 204 return; 205 } 206 float[] cn0Array = Arrays.copyOf(cn0s, numSv); 207 Arrays.sort(cn0Array); 208 mGnssPowerMetrics.reportSignalQuality(cn0Array, numSv); 209 if (numSv < 4) { 210 return; 211 } 212 if (cn0Array[numSv - 4] > 0.0) { 213 double top4AvgCn0 = 0.0; 214 for (int i = numSv - 4; i < numSv; i++) { 215 top4AvgCn0 += (double) cn0Array[i]; 216 } 217 top4AvgCn0 /= 4; 218 mTopFourAverageCn0Statistics.addItem(top4AvgCn0); 219 // Convert to mHz for accuracy 220 mTopFourAverageCn0DbmHzReportsStatistics.addItem(top4AvgCn0 * 1000); 221 } 222 } 223 /* Helper function to check if a SV is L5 */ isL5Sv(float carrierFreq)224 private static boolean isL5Sv(float carrierFreq) { 225 return (carrierFreq >= L5_CARRIER_FREQ_RANGE_LOW_HZ 226 && carrierFreq <= L5_CARRIER_FREQ_RANGE_HIGH_HZ); 227 } 228 229 /** 230 * Logs sv status data 231 */ logSvStatus(GnssStatus status)232 public void logSvStatus(GnssStatus status) { 233 boolean isL5 = false; 234 // Calculate SvStatus Information 235 for (int i = 0; i < status.getSatelliteCount(); i++) { 236 if (status.hasCarrierFrequencyHz(i)) { 237 mNumSvStatus++; 238 mSvStatusReports++; 239 isL5 = isL5Sv(status.getCarrierFrequencyHz(i)); 240 if (isL5) { 241 mNumL5SvStatus++; 242 mL5SvStatusReports++; 243 } 244 if (status.usedInFix(i)) { 245 mNumSvStatusUsedInFix++; 246 mSvStatusReportsUsedInFix++; 247 if (isL5) { 248 mNumL5SvStatusUsedInFix++; 249 mL5SvStatusReportsUsedInFix++; 250 } 251 } 252 } 253 } 254 } 255 256 /** 257 * Logs CN0 when at least 4 SVs are available L5 Only 258 */ logCn0L5(int svCount, float[] cn0s, float[] svCarrierFreqs)259 private void logCn0L5(int svCount, float[] cn0s, float[] svCarrierFreqs) { 260 if (svCount == 0 || cn0s == null || cn0s.length == 0 || cn0s.length < svCount 261 || svCarrierFreqs == null || svCarrierFreqs.length == 0 262 || svCarrierFreqs.length < svCount) { 263 return; 264 } 265 // Create array list of all L5 satellites in report. 266 ArrayList<Float> CnoL5Array = new ArrayList(); 267 for (int i = 0; i < svCount; i++) { 268 if (isL5Sv(svCarrierFreqs[i])) { 269 CnoL5Array.add(cn0s[i]); 270 } 271 } 272 if (CnoL5Array.size() == 0 || CnoL5Array.size() < 4) { 273 return; 274 } 275 int numSvL5 = CnoL5Array.size(); 276 Collections.sort(CnoL5Array); 277 if (CnoL5Array.get(numSvL5 - 4) > 0.0) { 278 double top4AvgCn0 = 0.0; 279 for (int i = numSvL5 - 4; i < numSvL5; i++) { 280 top4AvgCn0 += (double) CnoL5Array.get(i); 281 } 282 top4AvgCn0 /= 4; 283 mTopFourAverageCn0StatisticsL5.addItem(top4AvgCn0); 284 // Convert to mHz for accuracy 285 mL5TopFourAverageCn0DbmHzReportsStatistics.addItem(top4AvgCn0 * 1000); 286 } 287 return; 288 } 289 290 /** 291 * Logs that a constellation type has been observed. 292 */ logConstellationType(int constellationType)293 public void logConstellationType(int constellationType) { 294 if (constellationType >= mConstellationTypes.length) { 295 Log.e(TAG, "Constellation type " + constellationType + " is not valid."); 296 return; 297 } 298 mConstellationTypes[constellationType] = true; 299 } 300 301 /** 302 * Dumps GNSS metrics as a proto string 303 */ dumpGnssMetricsAsProtoString()304 public String dumpGnssMetricsAsProtoString() { 305 GnssLog msg = new GnssLog(); 306 if (mLocationFailureStatistics.getCount() > 0) { 307 msg.numLocationReportProcessed = mLocationFailureStatistics.getCount(); 308 msg.percentageLocationFailure = (int) (100.0 * mLocationFailureStatistics.getMean()); 309 } 310 if (mTimeToFirstFixSecStatistics.getCount() > 0) { 311 msg.numTimeToFirstFixProcessed = mTimeToFirstFixSecStatistics.getCount(); 312 msg.meanTimeToFirstFixSecs = (int) mTimeToFirstFixSecStatistics.getMean(); 313 msg.standardDeviationTimeToFirstFixSecs = 314 (int) mTimeToFirstFixSecStatistics.getStandardDeviation(); 315 } 316 if (mPositionAccuracyMeterStatistics.getCount() > 0) { 317 msg.numPositionAccuracyProcessed = mPositionAccuracyMeterStatistics.getCount(); 318 msg.meanPositionAccuracyMeters = (int) mPositionAccuracyMeterStatistics.getMean(); 319 msg.standardDeviationPositionAccuracyMeters = 320 (int) mPositionAccuracyMeterStatistics.getStandardDeviation(); 321 } 322 if (mTopFourAverageCn0Statistics.getCount() > 0) { 323 msg.numTopFourAverageCn0Processed = mTopFourAverageCn0Statistics.getCount(); 324 msg.meanTopFourAverageCn0DbHz = mTopFourAverageCn0Statistics.getMean(); 325 msg.standardDeviationTopFourAverageCn0DbHz = 326 mTopFourAverageCn0Statistics.getStandardDeviation(); 327 } 328 if (mNumSvStatus > 0) { 329 msg.numSvStatusProcessed = mNumSvStatus; 330 } 331 if (mNumL5SvStatus > 0) { 332 msg.numL5SvStatusProcessed = mNumL5SvStatus; 333 } 334 if (mNumSvStatusUsedInFix > 0) { 335 msg.numSvStatusUsedInFix = mNumSvStatusUsedInFix; 336 } 337 if (mNumL5SvStatusUsedInFix > 0) { 338 msg.numL5SvStatusUsedInFix = mNumL5SvStatusUsedInFix; 339 } 340 if (mTopFourAverageCn0StatisticsL5.getCount() > 0) { 341 msg.numL5TopFourAverageCn0Processed = mTopFourAverageCn0StatisticsL5.getCount(); 342 msg.meanL5TopFourAverageCn0DbHz = mTopFourAverageCn0StatisticsL5.getMean(); 343 msg.standardDeviationL5TopFourAverageCn0DbHz = 344 mTopFourAverageCn0StatisticsL5.getStandardDeviation(); 345 } 346 msg.powerMetrics = mGnssPowerMetrics.buildProto(); 347 msg.hardwareRevision = SystemProperties.get("ro.boot.revision", ""); 348 String s = Base64.encodeToString(GnssLog.toByteArray(msg), Base64.DEFAULT); 349 reset(); 350 return s; 351 } 352 353 /** 354 * Dumps GNSS Metrics as text 355 * 356 * @return GNSS Metrics 357 */ dumpGnssMetricsAsText()358 public String dumpGnssMetricsAsText() { 359 StringBuilder s = new StringBuilder(); 360 s.append("GNSS_KPI_START").append('\n'); 361 s.append(" KPI logging start time: ").append(mLogStartInElapsedRealTime).append("\n"); 362 s.append(" KPI logging end time: "); 363 TimeUtils.formatDuration(SystemClock.elapsedRealtimeNanos() / 1000000L, s); 364 s.append("\n"); 365 s.append(" Number of location reports: ").append( 366 mLocationFailureStatistics.getCount()).append("\n"); 367 if (mLocationFailureStatistics.getCount() > 0) { 368 s.append(" Percentage location failure: ").append( 369 100.0 * mLocationFailureStatistics.getMean()).append("\n"); 370 } 371 s.append(" Number of TTFF reports: ").append( 372 mTimeToFirstFixSecStatistics.getCount()).append("\n"); 373 if (mTimeToFirstFixSecStatistics.getCount() > 0) { 374 s.append(" TTFF mean (sec): ").append(mTimeToFirstFixSecStatistics.getMean()).append( 375 "\n"); 376 s.append(" TTFF standard deviation (sec): ").append( 377 mTimeToFirstFixSecStatistics.getStandardDeviation()).append("\n"); 378 } 379 s.append(" Number of position accuracy reports: ").append( 380 mPositionAccuracyMeterStatistics.getCount()).append("\n"); 381 if (mPositionAccuracyMeterStatistics.getCount() > 0) { 382 s.append(" Position accuracy mean (m): ").append( 383 mPositionAccuracyMeterStatistics.getMean()).append("\n"); 384 s.append(" Position accuracy standard deviation (m): ").append( 385 mPositionAccuracyMeterStatistics.getStandardDeviation()).append("\n"); 386 } 387 s.append(" Number of CN0 reports: ").append( 388 mTopFourAverageCn0Statistics.getCount()).append("\n"); 389 if (mTopFourAverageCn0Statistics.getCount() > 0) { 390 s.append(" Top 4 Avg CN0 mean (dB-Hz): ").append( 391 mTopFourAverageCn0Statistics.getMean()).append("\n"); 392 s.append(" Top 4 Avg CN0 standard deviation (dB-Hz): ").append( 393 mTopFourAverageCn0Statistics.getStandardDeviation()).append("\n"); 394 } 395 s.append(" Total number of sv status messages processed: ").append( 396 mNumSvStatus).append("\n"); 397 s.append(" Total number of L5 sv status messages processed: ").append( 398 mNumL5SvStatus).append("\n"); 399 s.append(" Total number of sv status messages processed, " 400 + "where sv is used in fix: ").append( 401 mNumSvStatusUsedInFix).append("\n"); 402 s.append(" Total number of L5 sv status messages processed, " 403 + "where sv is used in fix: ").append( 404 mNumL5SvStatusUsedInFix).append("\n"); 405 s.append(" Number of L5 CN0 reports: ").append( 406 mTopFourAverageCn0StatisticsL5.getCount()).append("\n"); 407 if (mTopFourAverageCn0StatisticsL5.getCount() > 0) { 408 s.append(" L5 Top 4 Avg CN0 mean (dB-Hz): ").append( 409 mTopFourAverageCn0StatisticsL5.getMean()).append("\n"); 410 s.append(" L5 Top 4 Avg CN0 standard deviation (dB-Hz): ").append( 411 mTopFourAverageCn0StatisticsL5.getStandardDeviation()).append("\n"); 412 } 413 s.append(" Used-in-fix constellation types: "); 414 for (int i = 0; i < mConstellationTypes.length; i++) { 415 if (mConstellationTypes[i]) { 416 s.append(GnssStatus.constellationTypeToString(i)).append(" "); 417 } 418 } 419 s.append("\n"); 420 s.append("GNSS_KPI_END").append("\n"); 421 GpsBatteryStats stats = mGnssPowerMetrics.getGpsBatteryStats(); 422 if (stats != null) { 423 s.append("Power Metrics").append("\n"); 424 s.append(" Time on battery (min): ").append( 425 stats.getLoggingDurationMs() / ((double) DateUtils.MINUTE_IN_MILLIS)).append( 426 "\n"); 427 long[] t = stats.getTimeInGpsSignalQualityLevel(); 428 if (t != null && t.length == NUM_GPS_SIGNAL_QUALITY_LEVELS) { 429 s.append(" Amount of time (while on battery) Top 4 Avg CN0 > " 430 + GnssPowerMetrics.POOR_TOP_FOUR_AVG_CN0_THRESHOLD_DB_HZ 431 + " dB-Hz (min): ").append( 432 t[1] / ((double) DateUtils.MINUTE_IN_MILLIS)).append("\n"); 433 s.append(" Amount of time (while on battery) Top 4 Avg CN0 <= " 434 + GnssPowerMetrics.POOR_TOP_FOUR_AVG_CN0_THRESHOLD_DB_HZ 435 + " dB-Hz (min): ").append( 436 t[0] / ((double) DateUtils.MINUTE_IN_MILLIS)).append("\n"); 437 } 438 s.append(" Energy consumed while on battery (mAh): ").append( 439 stats.getEnergyConsumedMaMs() / ((double) DateUtils.HOUR_IN_MILLIS)).append( 440 "\n"); 441 } 442 s.append("Hardware Version: ").append(SystemProperties.get("ro.boot.revision", "")).append( 443 "\n"); 444 return s.toString(); 445 } 446 reset()447 private void reset() { 448 StringBuilder s = new StringBuilder(); 449 TimeUtils.formatDuration(SystemClock.elapsedRealtimeNanos() / 1000000L, s); 450 mLogStartInElapsedRealTime = s.toString(); 451 mLocationFailureStatistics.reset(); 452 mTimeToFirstFixSecStatistics.reset(); 453 mPositionAccuracyMeterStatistics.reset(); 454 mTopFourAverageCn0Statistics.reset(); 455 resetConstellationTypes(); 456 mTopFourAverageCn0StatisticsL5.reset(); 457 mNumSvStatus = 0; 458 mNumL5SvStatus = 0; 459 mNumSvStatusUsedInFix = 0; 460 mNumL5SvStatusUsedInFix = 0; 461 } 462 463 /** Resets {@link #mConstellationTypes} as an all-false boolean array. */ resetConstellationTypes()464 public void resetConstellationTypes() { 465 mConstellationTypes = new boolean[GnssStatus.CONSTELLATION_COUNT]; 466 } 467 468 /** Thread-safe class for storing statistics */ 469 private static class Statistics { 470 471 private int mCount; 472 private double mSum; 473 private double mSumSquare; 474 private long mLongSum; 475 476 /** Resets statistics */ reset()477 public synchronized void reset() { 478 mCount = 0; 479 mSum = 0.0; 480 mSumSquare = 0.0; 481 mLongSum = 0; 482 } 483 484 /** Adds an item */ addItem(double item)485 public synchronized void addItem(double item) { 486 mCount++; 487 mSum += item; 488 mSumSquare += item * item; 489 mLongSum += item; 490 } 491 492 /** Returns number of items added */ getCount()493 public synchronized int getCount() { 494 return mCount; 495 } 496 497 /** Returns mean */ getMean()498 public synchronized double getMean() { 499 return mSum / mCount; 500 } 501 502 /** Returns standard deviation */ getStandardDeviation()503 public synchronized double getStandardDeviation() { 504 double m = mSum / mCount; 505 m = m * m; 506 double v = mSumSquare / mCount; 507 if (v > m) { 508 return Math.sqrt(v - m); 509 } 510 return 0; 511 } 512 513 /** Returns long sum */ getLongSum()514 public synchronized long getLongSum() { 515 return mLongSum; 516 } 517 } 518 519 /* Class for handling GNSS power related metrics */ 520 private class GnssPowerMetrics { 521 522 /* Threshold for Top Four Average CN0 below which GNSS signal quality is declared poor */ 523 public static final double POOR_TOP_FOUR_AVG_CN0_THRESHOLD_DB_HZ = 20.0; 524 525 /* Minimum change in Top Four Average CN0 needed to trigger a report */ 526 private static final double REPORTING_THRESHOLD_DB_HZ = 1.0; 527 528 /* BatteryStats API */ 529 private final IBatteryStats mBatteryStats; 530 531 /* Last reported Top Four Average CN0 */ 532 private double mLastAverageCn0; 533 534 /* Last reported signal quality bin (based on Top Four Average CN0) */ 535 private int mLastSignalLevel; 536 GnssPowerMetrics(IBatteryStats stats)537 private GnssPowerMetrics(IBatteryStats stats) { 538 mBatteryStats = stats; 539 // Used to initialize the variable to a very small value (unachievable in practice) 540 // so that 541 // the first CNO report will trigger an update to BatteryStats 542 mLastAverageCn0 = -100.0; 543 mLastSignalLevel = GPS_SIGNAL_QUALITY_UNKNOWN; 544 } 545 546 /** 547 * Builds power metrics proto buf. This is included in the gnss proto buf. 548 * 549 * @return PowerMetrics 550 */ buildProto()551 public PowerMetrics buildProto() { 552 PowerMetrics p = new PowerMetrics(); 553 GpsBatteryStats stats = mGnssPowerMetrics.getGpsBatteryStats(); 554 if (stats != null) { 555 p.loggingDurationMs = stats.getLoggingDurationMs(); 556 p.energyConsumedMah = 557 stats.getEnergyConsumedMaMs() / ((double) DateUtils.HOUR_IN_MILLIS); 558 long[] t = stats.getTimeInGpsSignalQualityLevel(); 559 p.timeInSignalQualityLevelMs = new long[t.length]; 560 for (int i = 0; i < t.length; i++) { 561 p.timeInSignalQualityLevelMs[i] = t[i]; 562 } 563 } 564 return p; 565 } 566 567 /** 568 * Returns the GPS power stats 569 * 570 * @return GpsBatteryStats 571 */ getGpsBatteryStats()572 public GpsBatteryStats getGpsBatteryStats() { 573 try { 574 return mBatteryStats.getGpsBatteryStats(); 575 } catch (Exception e) { 576 Log.w(TAG, "Exception", e); 577 return null; 578 } 579 } 580 581 /** 582 * Reports signal quality to BatteryStats. Signal quality is based on Top four average CN0. 583 * If 584 * the number of SVs seen is less than 4, then signal quality is the average CN0. 585 * Changes are reported only if the average CN0 changes by more than 586 * REPORTING_THRESHOLD_DB_HZ. 587 */ reportSignalQuality(float[] ascendingCN0Array, int numSv)588 public void reportSignalQuality(float[] ascendingCN0Array, int numSv) { 589 double avgCn0 = 0.0; 590 if (numSv > 0) { 591 for (int i = Math.max(0, numSv - 4); i < numSv; i++) { 592 avgCn0 += (double) ascendingCN0Array[i]; 593 } 594 avgCn0 /= Math.min(numSv, 4); 595 } 596 if (Math.abs(avgCn0 - mLastAverageCn0) < REPORTING_THRESHOLD_DB_HZ) { 597 return; 598 } 599 int signalLevel = getSignalLevel(avgCn0); 600 if (signalLevel != mLastSignalLevel) { 601 FrameworkStatsLog.write(FrameworkStatsLog.GPS_SIGNAL_QUALITY_CHANGED, signalLevel); 602 mLastSignalLevel = signalLevel; 603 } 604 try { 605 mBatteryStats.noteGpsSignalQuality(signalLevel); 606 mLastAverageCn0 = avgCn0; 607 } catch (Exception e) { 608 Log.w(TAG, "Exception", e); 609 } 610 } 611 612 /** 613 * Obtains signal level based on CN0 614 */ getSignalLevel(double cn0)615 private int getSignalLevel(double cn0) { 616 if (cn0 > POOR_TOP_FOUR_AVG_CN0_THRESHOLD_DB_HZ) { 617 return GnssMetrics.GPS_SIGNAL_QUALITY_GOOD; 618 } 619 return GnssMetrics.GPS_SIGNAL_QUALITY_POOR; 620 } 621 } 622 registerGnssStats()623 private void registerGnssStats() { 624 StatsPullAtomCallbackImpl pullAtomCallback = new StatsPullAtomCallbackImpl(); 625 mStatsManager.setPullAtomCallback( 626 FrameworkStatsLog.GNSS_STATS, 627 null, // use default PullAtomMetadata values 628 ConcurrentUtils.DIRECT_EXECUTOR, pullAtomCallback); 629 } 630 631 /** 632 * Stats Pull Atom Callback 633 * Calls the pull method to fill out gnss stats 634 */ 635 private class StatsPullAtomCallbackImpl implements StatsManager.StatsPullAtomCallback { 636 @Override onPullAtom(int atomTag, List<StatsEvent> data)637 public int onPullAtom(int atomTag, List<StatsEvent> data) { 638 if (atomTag != FrameworkStatsLog.GNSS_STATS) { 639 throw new UnsupportedOperationException("Unknown tagId = " + atomTag); 640 } 641 StatsEvent e = StatsEvent.newBuilder() 642 .setAtomId(atomTag) 643 .writeLong(mLocationFailureReportsStatistics.getCount()) 644 .writeLong(mLocationFailureReportsStatistics.getLongSum()) 645 .writeLong(mTimeToFirstFixMilliSReportsStatistics.getCount()) 646 .writeLong(mTimeToFirstFixMilliSReportsStatistics.getLongSum()) 647 .writeLong(mPositionAccuracyMetersReportsStatistics.getCount()) 648 .writeLong(mPositionAccuracyMetersReportsStatistics.getLongSum()) 649 .writeLong(mTopFourAverageCn0DbmHzReportsStatistics.getCount()) 650 .writeLong(mTopFourAverageCn0DbmHzReportsStatistics.getLongSum()) 651 .writeLong(mL5TopFourAverageCn0DbmHzReportsStatistics.getCount()) 652 .writeLong(mL5TopFourAverageCn0DbmHzReportsStatistics.getLongSum()) 653 .writeLong(mSvStatusReports) 654 .writeLong(mSvStatusReportsUsedInFix) 655 .writeLong(mL5SvStatusReports) 656 .writeLong(mL5SvStatusReportsUsedInFix) 657 .build(); 658 data.add(e); 659 return StatsManager.PULL_SUCCESS; 660 } 661 } 662 } 663