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