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