1 /*
2  * Copyright (C) 2019 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.metrics;
18 
19 import static com.android.internal.telephony.metrics.TelephonyMetrics.toCallQualityProto;
20 
21 import android.os.Build;
22 import android.telephony.CallQuality;
23 import android.telephony.CellInfo;
24 import android.telephony.CellSignalStrengthLte;
25 import android.telephony.Rlog;
26 import android.telephony.SignalStrength;
27 import android.util.Pair;
28 
29 import com.android.internal.telephony.Phone;
30 import com.android.internal.telephony.ServiceStateTracker;
31 import com.android.internal.telephony.nano.TelephonyProto.TelephonyCallSession;
32 
33 import java.util.ArrayList;
34 
35 /**
36  * CallQualityMetrics is a utility for tracking the CallQuality during an ongoing call session. It
37  * processes snapshots throughout the call to keep track of info like the best and worst
38  * ServiceStates, durations of good and bad quality, and other summary statistics.
39  */
40 public class CallQualityMetrics {
41 
42     private static final String TAG = CallQualityMetrics.class.getSimpleName();
43 
44     // certain metrics are only logged on userdebug
45     private static final boolean USERDEBUG_MODE = Build.IS_USERDEBUG;
46 
47     // We only log the first MAX_SNAPSHOTS changes to CallQuality
48     private static final int MAX_SNAPSHOTS = 5;
49 
50     // value of mCallQualityState which means the CallQuality is EXCELLENT/GOOD/FAIR
51     private static final int GOOD_QUALITY = 0;
52 
53     // value of mCallQualityState which means the CallQuality is BAD/POOR
54     private static final int BAD_QUALITY = 1;
55 
56     private Phone mPhone;
57 
58     /** Snapshots of the call quality and SignalStrength (LTE-SNR for IMS calls) */
59     // mUlSnapshots holds snapshots from uplink call quality changes. We log take snapshots of the
60     // first MAX_SNAPSHOTS transitions between good and bad quality
61     private ArrayList<Pair<CallQuality, Integer>> mUlSnapshots = new ArrayList<>();
62     // mDlSnapshots holds snapshots from downlink call quality changes. We log take snapshots of
63     // the first MAX_SNAPSHOTS transitions between good and bad quality
64     private ArrayList<Pair<CallQuality, Integer>> mDlSnapshots = new ArrayList<>();
65 
66     // Current downlink call quality
67     private int mDlCallQualityState = GOOD_QUALITY;
68 
69     // Current uplink call quality
70     private int mUlCallQualityState = GOOD_QUALITY;
71 
72     // The last logged CallQuality
73     private CallQuality mLastCallQuality;
74 
75     /** Snapshots taken at best and worst SignalStrengths*/
76     private Pair<CallQuality, Integer> mWorstSsWithGoodDlQuality;
77     private Pair<CallQuality, Integer> mBestSsWithGoodDlQuality;
78     private Pair<CallQuality, Integer> mWorstSsWithBadDlQuality;
79     private Pair<CallQuality, Integer> mBestSsWithBadDlQuality;
80     private Pair<CallQuality, Integer> mWorstSsWithGoodUlQuality;
81     private Pair<CallQuality, Integer> mBestSsWithGoodUlQuality;
82     private Pair<CallQuality, Integer> mWorstSsWithBadUlQuality;
83     private Pair<CallQuality, Integer> mBestSsWithBadUlQuality;
84 
85     /** Total durations of good and bad quality time for uplink and downlink */
86     private int mTotalDlGoodQualityTimeMs = 0;
87     private int mTotalDlBadQualityTimeMs = 0;
88     private int mTotalUlGoodQualityTimeMs = 0;
89     private int mTotalUlBadQualityTimeMs = 0;
90 
91     /**
92      * Construct a CallQualityMetrics object to be used to keep track of call quality for a single
93      * call session.
94      */
CallQualityMetrics(Phone phone)95     public CallQualityMetrics(Phone phone) {
96         mPhone = phone;
97         mLastCallQuality = new CallQuality();
98     }
99 
100     /**
101      * Called when call quality changes.
102      */
saveCallQuality(CallQuality cq)103     public void saveCallQuality(CallQuality cq) {
104         if (cq.getUplinkCallQualityLevel() == CallQuality.CALL_QUALITY_NOT_AVAILABLE
105                 || cq.getDownlinkCallQualityLevel() == CallQuality.CALL_QUALITY_NOT_AVAILABLE) {
106             return;
107         }
108 
109         // uplink and downlink call quality are tracked separately
110         int newUlCallQualityState = BAD_QUALITY;
111         int newDlCallQualityState = BAD_QUALITY;
112         if (isGoodQuality(cq.getUplinkCallQualityLevel())) {
113             newUlCallQualityState = GOOD_QUALITY;
114         }
115         if (isGoodQuality(cq.getDownlinkCallQualityLevel())) {
116             newDlCallQualityState = GOOD_QUALITY;
117         }
118 
119         if (USERDEBUG_MODE) {
120             if (newUlCallQualityState != mUlCallQualityState) {
121                 mUlSnapshots = addSnapshot(cq, mUlSnapshots);
122             }
123             if (newDlCallQualityState != mDlCallQualityState) {
124                 mDlSnapshots = addSnapshot(cq, mDlSnapshots);
125             }
126         }
127 
128         updateTotalDurations(newDlCallQualityState, newUlCallQualityState, cq);
129 
130         updateMinAndMaxSignalStrengthSnapshots(newDlCallQualityState, newUlCallQualityState, cq);
131 
132         mUlCallQualityState = newUlCallQualityState;
133         mDlCallQualityState = newDlCallQualityState;
134         mLastCallQuality = cq;
135     }
136 
isGoodQuality(int callQualityLevel)137     private static boolean isGoodQuality(int callQualityLevel) {
138         return callQualityLevel < CallQuality.CALL_QUALITY_BAD;
139     }
140 
141     /**
142      * Save a snapshot of the call quality and signal strength. This can be called with uplink or
143      * downlink call quality level.
144      */
addSnapshot(CallQuality cq, ArrayList<Pair<CallQuality, Integer>> snapshots)145     private ArrayList<Pair<CallQuality, Integer>> addSnapshot(CallQuality cq,
146             ArrayList<Pair<CallQuality, Integer>> snapshots) {
147         if (snapshots.size() < MAX_SNAPSHOTS) {
148             Integer ss = getLteSnr();
149             snapshots.add(Pair.create(cq, ss));
150         }
151         return snapshots;
152     }
153 
154     /**
155      * Updates the running total duration of good and bad call quality for uplink and downlink.
156      */
updateTotalDurations(int newDlCallQualityState, int newUlCallQualityState, CallQuality cq)157     private void updateTotalDurations(int newDlCallQualityState, int newUlCallQualityState,
158             CallQuality cq) {
159         int timePassed = cq.getCallDuration() - mLastCallQuality.getCallDuration();
160         if (newDlCallQualityState == GOOD_QUALITY) {
161             mTotalDlGoodQualityTimeMs += timePassed;
162         } else {
163             mTotalDlBadQualityTimeMs += timePassed;
164         }
165 
166         if (newUlCallQualityState == GOOD_QUALITY) {
167             mTotalUlGoodQualityTimeMs += timePassed;
168         } else {
169             mTotalUlBadQualityTimeMs += timePassed;
170         }
171     }
172 
173     /**
174      * Updates the snapshots saved when signal strength is highest and lowest while the call quality
175      * is good and bad for both uplink and downlink call quality.
176      * <p>
177      * At the end of the call we should have:
178      *  - for both UL and DL:
179      *     - snapshot of the best signal strength with bad call quality
180      *     - snapshot of the worst signal strength with bad call quality
181      *     - snapshot of the best signal strength with good call quality
182      *     - snapshot of the worst signal strength with good call quality
183      */
updateMinAndMaxSignalStrengthSnapshots(int newDlCallQualityState, int newUlCallQualityState, CallQuality cq)184     private void updateMinAndMaxSignalStrengthSnapshots(int newDlCallQualityState,
185             int newUlCallQualityState, CallQuality cq) {
186         Integer ss = getLteSnr();
187         if (ss.equals(CellInfo.UNAVAILABLE)) {
188             return;
189         }
190 
191         // downlink
192         if (newDlCallQualityState == GOOD_QUALITY) {
193             if (mWorstSsWithGoodDlQuality == null || ss < mWorstSsWithGoodDlQuality.second) {
194                 mWorstSsWithGoodDlQuality = Pair.create(cq, ss);
195             }
196             if (mBestSsWithGoodDlQuality == null || ss > mBestSsWithGoodDlQuality.second) {
197                 mBestSsWithGoodDlQuality = Pair.create(cq, ss);
198             }
199         } else {
200             if (mWorstSsWithBadDlQuality == null || ss < mWorstSsWithBadDlQuality.second) {
201                 mWorstSsWithBadDlQuality = Pair.create(cq, ss);
202             }
203             if (mBestSsWithBadDlQuality == null || ss > mBestSsWithBadDlQuality.second) {
204                 mBestSsWithBadDlQuality = Pair.create(cq, ss);
205             }
206         }
207 
208         // uplink
209         if (newUlCallQualityState == GOOD_QUALITY) {
210             if (mWorstSsWithGoodUlQuality == null || ss < mWorstSsWithGoodUlQuality.second) {
211                 mWorstSsWithGoodUlQuality = Pair.create(cq, ss);
212             }
213             if (mBestSsWithGoodUlQuality == null || ss > mBestSsWithGoodUlQuality.second) {
214                 mBestSsWithGoodUlQuality = Pair.create(cq, ss);
215             }
216         } else {
217             if (mWorstSsWithBadUlQuality == null || ss < mWorstSsWithBadUlQuality.second) {
218                 mWorstSsWithBadUlQuality = Pair.create(cq, ss);
219             }
220             if (mBestSsWithBadUlQuality == null || ss > mBestSsWithBadUlQuality.second) {
221                 mBestSsWithBadUlQuality = Pair.create(cq, ss);
222             }
223         }
224     }
225 
226     // Returns the LTE signal to noise ratio, or 0 if unavailable
getLteSnr()227     private Integer getLteSnr() {
228         ServiceStateTracker sst = mPhone.getDefaultPhone().getServiceStateTracker();
229         if (sst == null) {
230             Rlog.e(TAG, "getLteSnr: unable to get SST for phone " + mPhone.getPhoneId());
231             return CellInfo.UNAVAILABLE;
232         }
233 
234         SignalStrength ss = sst.getSignalStrength();
235         if (ss == null) {
236             Rlog.e(TAG, "getLteSnr: unable to get SignalStrength for phone " + mPhone.getPhoneId());
237             return CellInfo.UNAVAILABLE;
238         }
239 
240         // There may be multiple CellSignalStrengthLte, so try to use one with available SNR
241         for (CellSignalStrengthLte lteSs : ss.getCellSignalStrengths(CellSignalStrengthLte.class)) {
242             int snr = lteSs.getRssnr();
243             if (snr != CellInfo.UNAVAILABLE) {
244                 return snr;
245             }
246         }
247 
248         return CellInfo.UNAVAILABLE;
249     }
250 
toProto(int ss)251     private static TelephonyCallSession.Event.SignalStrength toProto(int ss) {
252         TelephonyCallSession.Event.SignalStrength ret =
253                 new TelephonyCallSession.Event.SignalStrength();
254         ret.lteSnr = ss;
255         return ret;
256     }
257 
258     /**
259      * Return the full downlink CallQualitySummary using the saved CallQuality records.
260      */
getCallQualitySummaryDl()261     public TelephonyCallSession.Event.CallQualitySummary getCallQualitySummaryDl() {
262         TelephonyCallSession.Event.CallQualitySummary summary =
263                 new TelephonyCallSession.Event.CallQualitySummary();
264         summary.totalGoodQualityDurationInSeconds = mTotalDlGoodQualityTimeMs / 1000;
265         summary.totalBadQualityDurationInSeconds = mTotalDlBadQualityTimeMs / 1000;
266         // This value could be different from mLastCallQuality.getCallDuration if we support
267         // handover from IMS->CS->IMS, but this is currently not possible
268         // TODO(b/130302396) this also may be possible when we put a call on hold and continue with
269         // another call
270         summary.totalDurationWithQualityInformationInSeconds =
271                 mLastCallQuality.getCallDuration() / 1000;
272         if (mWorstSsWithGoodDlQuality != null) {
273             summary.snapshotOfWorstSsWithGoodQuality =
274                     toCallQualityProto(mWorstSsWithGoodDlQuality.first);
275             summary.worstSsWithGoodQuality = toProto(mWorstSsWithGoodDlQuality.second);
276         }
277         if (mBestSsWithGoodDlQuality != null) {
278             summary.snapshotOfBestSsWithGoodQuality =
279                     toCallQualityProto(mBestSsWithGoodDlQuality.first);
280             summary.bestSsWithGoodQuality = toProto(mBestSsWithGoodDlQuality.second);
281         }
282         if (mWorstSsWithBadDlQuality != null) {
283             summary.snapshotOfWorstSsWithBadQuality =
284                     toCallQualityProto(mWorstSsWithBadDlQuality.first);
285             summary.worstSsWithBadQuality = toProto(mWorstSsWithBadDlQuality.second);
286         }
287         if (mBestSsWithBadDlQuality != null) {
288             summary.snapshotOfBestSsWithBadQuality =
289                     toCallQualityProto(mBestSsWithBadDlQuality.first);
290             summary.bestSsWithBadQuality = toProto(mBestSsWithBadDlQuality.second);
291         }
292         summary.snapshotOfEnd = toCallQualityProto(mLastCallQuality);
293         return summary;
294     }
295 
296     /**
297      * Return the full uplink CallQualitySummary using the saved CallQuality records.
298      */
getCallQualitySummaryUl()299     public TelephonyCallSession.Event.CallQualitySummary getCallQualitySummaryUl() {
300         TelephonyCallSession.Event.CallQualitySummary summary =
301                 new TelephonyCallSession.Event.CallQualitySummary();
302         summary.totalGoodQualityDurationInSeconds = mTotalUlGoodQualityTimeMs / 1000;
303         summary.totalBadQualityDurationInSeconds = mTotalUlBadQualityTimeMs / 1000;
304         // This value could be different from mLastCallQuality.getCallDuration if we support
305         // handover from IMS->CS->IMS, but this is currently not possible
306         // TODO(b/130302396) this also may be possible when we put a call on hold and continue with
307         // another call
308         summary.totalDurationWithQualityInformationInSeconds =
309                 mLastCallQuality.getCallDuration() / 1000;
310         if (mWorstSsWithGoodUlQuality != null) {
311             summary.snapshotOfWorstSsWithGoodQuality =
312                     toCallQualityProto(mWorstSsWithGoodUlQuality.first);
313             summary.worstSsWithGoodQuality = toProto(mWorstSsWithGoodUlQuality.second);
314         }
315         if (mBestSsWithGoodUlQuality != null) {
316             summary.snapshotOfBestSsWithGoodQuality =
317                     toCallQualityProto(mBestSsWithGoodUlQuality.first);
318             summary.bestSsWithGoodQuality = toProto(mBestSsWithGoodUlQuality.second);
319         }
320         if (mWorstSsWithBadUlQuality != null) {
321             summary.snapshotOfWorstSsWithBadQuality =
322                     toCallQualityProto(mWorstSsWithBadUlQuality.first);
323             summary.worstSsWithBadQuality = toProto(mWorstSsWithBadUlQuality.second);
324         }
325         if (mBestSsWithBadUlQuality != null) {
326             summary.snapshotOfBestSsWithBadQuality =
327                     toCallQualityProto(mBestSsWithBadUlQuality.first);
328             summary.bestSsWithBadQuality = toProto(mBestSsWithBadUlQuality.second);
329         }
330         summary.snapshotOfEnd = toCallQualityProto(mLastCallQuality);
331         return summary;
332     }
333 
334     @Override
toString()335     public String toString() {
336         StringBuilder sb = new StringBuilder();
337         sb.append("[CallQualityMetrics phone ");
338         sb.append(mPhone.getPhoneId());
339         sb.append(" mUlSnapshots: {");
340         for (Pair<CallQuality, Integer> snapshot : mUlSnapshots) {
341             sb.append(" {cq=");
342             sb.append(snapshot.first);
343             sb.append(" ss=");
344             sb.append(snapshot.second);
345             sb.append("}");
346         }
347         sb.append("}");
348         sb.append(" mDlSnapshots:{");
349         for (Pair<CallQuality, Integer> snapshot : mDlSnapshots) {
350             sb.append(" {cq=");
351             sb.append(snapshot.first);
352             sb.append(" ss=");
353             sb.append(snapshot.second);
354             sb.append("}");
355         }
356         sb.append("}");
357         sb.append(" ");
358         sb.append(" mTotalDlGoodQualityTimeMs: ");
359         sb.append(mTotalDlGoodQualityTimeMs);
360         sb.append(" mTotalDlBadQualityTimeMs: ");
361         sb.append(mTotalDlBadQualityTimeMs);
362         sb.append(" mTotalUlGoodQualityTimeMs: ");
363         sb.append(mTotalUlGoodQualityTimeMs);
364         sb.append(" mTotalUlBadQualityTimeMs: ");
365         sb.append(mTotalUlBadQualityTimeMs);
366         sb.append("]");
367         return sb.toString();
368     }
369 }
370