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