1 /*
2  * Copyright (C) 2022 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.telephony.qns;
18 
19 import static com.android.telephony.qns.QnsConstants.INVALID_ID;
20 
21 import android.annotation.NonNull;
22 import android.net.NetworkCapabilities;
23 import android.os.Handler;
24 import android.os.HandlerThread;
25 import android.os.Looper;
26 import android.os.Message;
27 import android.telephony.AccessNetworkConstants;
28 import android.telephony.Annotation;
29 import android.telephony.CallQuality;
30 import android.telephony.CallState;
31 import android.telephony.PreciseCallState;
32 import android.telephony.PreciseDataConnectionState;
33 import android.telephony.TelephonyManager;
34 import android.telephony.ims.ImsCallProfile;
35 import android.telephony.ims.MediaQualityStatus;
36 import android.util.Log;
37 import android.util.SparseArray;
38 
39 import com.android.internal.annotations.VisibleForTesting;
40 
41 import java.util.ArrayList;
42 import java.util.List;
43 import java.util.function.Consumer;
44 
45 /**
46  * Tracking IMS Call status and update call type changed event to ANE.
47  */
48 public class QnsCallStatusTracker {
49     private final String mLogTag;
50     private QnsTelephonyListener mTelephonyListener;
51     private QnsCarrierConfigManager mConfigManager;
52     private List<CallState> mCallStates = new ArrayList<>();
53     private QnsRegistrant mCallTypeChangedEventListener;
54     private QnsRegistrant mEmergencyCallTypeChangedEventListener;
55     private final QnsTimer mQnsTimer;
56     private int mLastNormalCallType = QnsConstants.CALL_TYPE_IDLE;
57     private int mLastEmergencyCallType = QnsConstants.CALL_TYPE_IDLE;
58     private boolean mEmergencyOverIms;
59     private ActiveCallTracker mActiveCallTracker;
60     private Consumer<List<CallState>> mCallStatesConsumer =
61             callStateList -> updateCallState(callStateList);
62     private Consumer<Integer> mSrvccStateConsumer = state -> onSrvccStateChangedInternal(state);
63     private Consumer<MediaQualityStatus> mMediaQualityStatusConsumer =
64             status -> mActiveCallTracker.onMediaQualityStatusChanged(status);
65 
66     static class CallQualityBlock {
67         int mUpLinkLevel;
68         int mDownLinkLevel;
69         long mCreatedElapsedTime;
70         long mDurationMillis;
CallQualityBlock(int uplinkLevel, int downLinkLevel, long createdElapsedTime)71         CallQualityBlock(int uplinkLevel, int downLinkLevel, long createdElapsedTime) {
72             mUpLinkLevel = uplinkLevel;
73             mDownLinkLevel = downLinkLevel;
74             mCreatedElapsedTime = createdElapsedTime;
75         }
76 
getUpLinkQualityVolume()77         long getUpLinkQualityVolume() {
78             if (mDurationMillis > 0) {
79                 return mUpLinkLevel * mDurationMillis;
80             } else {
81                 long now = QnsUtils.getSystemElapsedRealTime();
82                 return (now - mCreatedElapsedTime) * mUpLinkLevel;
83             }
84         }
85 
getDownLinkQualityVolume()86         long getDownLinkQualityVolume() {
87             if (mDurationMillis > 0) {
88                 return mDownLinkLevel * mDurationMillis;
89             } else {
90                 long now = QnsUtils.getSystemElapsedRealTime();
91                 return (now - mCreatedElapsedTime) * mDownLinkLevel;
92             }
93         }
94     }
95 
96     class ActiveCallTracker {
97         private static final int EVENT_DATA_CONNECTION_STATUS_CHANGED = 3300;
98 
99         @QnsConstants.QnsCallType
100         private int mCallType = QnsConstants.CALL_TYPE_IDLE;
101         @Annotation.NetCapability
102         private int mNetCapability = QnsConstants.INVALID_VALUE;
103         private QnsRegistrantList mLowMediaQualityListeners = new QnsRegistrantList();
104         private int mAccessNetwork = AccessNetworkConstants.AccessNetworkType.UNKNOWN;
105         private int mTransportType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
106         private SparseArray<CallQuality> mCallQualities = new SparseArray();
107         private TransportQuality mCurrentQuality;
108         /** A list of TransportQuality for each Transport type */
109         private SparseArray<List<TransportQuality>> mTransportQualityArray = new SparseArray<>();
110         private boolean mWwanAvailable = false;
111         private boolean mWlanAvailable = false;
112 
113         private boolean mMediaThresholdBreached = false;
114         private HandlerThread mHandlerThread;
115         private ActiveCallTrackerHandler mActiveCallHandler;
116         private MediaLowQualityHandler mLowQualityHandler;
117         private String mLogTag;
118 
119         private class ActiveCallTrackerHandler extends Handler {
ActiveCallTrackerHandler(Looper l)120             ActiveCallTrackerHandler(Looper l) {
121                 super(l);
122             }
123 
124             @Override
handleMessage(Message message)125             public void handleMessage(Message message) {
126                 QnsAsyncResult ar;
127                 int transportType;
128                 Log.d(mLogTag, "handleMessage : " + message.what);
129                 switch (message.what) {
130                     case EVENT_DATA_CONNECTION_STATUS_CHANGED:
131                         ar = (QnsAsyncResult) message.obj;
132                         onDataConnectionStatusChanged(
133                                 (PreciseDataConnectionState) ar.mResult);
134                         break;
135 
136                     default:
137                         Log.d(mLogTag, "unHandleMessage : " + message.what);
138                         break;
139                 }
140 
141             }
142         }
143 
144         /** Tracking low quality status */
145         private class MediaLowQualityHandler extends Handler {
146             private static final int EVENT_MEDIA_QUALITY_CHANGED = 3401;
147             private static final int EVENT_PACKET_LOSS_TIMER_EXPIRED = 3402;
148             private static final int EVENT_HYSTERESIS_FOR_NORMAL_QUALITY = 3403;
149             private static final int EVENT_POLLING_CHECK_LOW_QUALITY = 3404;
150 
151             private static final int STATE_NORMAL_QUALITY = 0;
152             private static final int STATE_SUSPECT_LOW_QUALITY = 1;
153             private static final int STATE_LOW_QUALITY = 2;
154 
155             private static final int HYSTERESIS_TIME_NORMAL_QUALITY_MILLIS = 3000;
156             private static final int LOW_QUALITY_CHECK_INTERVAL_MILLIS = 15000;
157             private static final int LOW_QUALITY_CHECK_AFTER_HO_MILLIS = 3000;
158             private static final int LOW_QUALITY_REPORTED_TIME_INITIAL_VALUE = -1;
159 
160             private int mState = STATE_NORMAL_QUALITY;
161             private int mPacketLossTimerId = INVALID_ID;
162             private int mHysteresisTimerId = INVALID_ID;
163             private int mPollingCheckTimerId = INVALID_ID;
164             private MediaQualityStatus mMediaQualityStatus;
165             private String mTag;
166 
MediaLowQualityHandler(Looper l)167             MediaLowQualityHandler(Looper l) {
168                 super(l);
169                 mTag = mLogTag + "_LQH";
170             }
171 
172             @Override
handleMessage(Message message)173             public void handleMessage(Message message) {
174                 Log.d(mTag, "handleMessage : " + message.what);
175                 switch (message.what) {
176                     case EVENT_MEDIA_QUALITY_CHANGED:
177                         MediaQualityStatus status = (MediaQualityStatus) message.obj;
178                         onMediaQualityChanged(status);
179                         break;
180 
181                     case EVENT_PACKET_LOSS_TIMER_EXPIRED:
182                         onPacketLossTimerExpired(message.arg1);
183                         break;
184 
185                     case EVENT_HYSTERESIS_FOR_NORMAL_QUALITY:
186                         exitLowQualityState();
187                         break;
188 
189                     case EVENT_POLLING_CHECK_LOW_QUALITY:
190                         checkLowQuality();
191                         break;
192 
193                     default:
194                         Log.d(mLogTag, "unHandleMessage : " + message.what);
195                         break;
196                 }
197             }
198 
onMediaQualityChanged(MediaQualityStatus status)199             private void onMediaQualityChanged(MediaQualityStatus status) {
200                 Log.d(mTag, "onMediaQualityChanged " + status);
201                 int reason = thresholdBreached(status);
202                 boolean needNotify = false;
203                 if (reason == 0) {
204                     // Threshold not breached.
205                     mMediaQualityStatus = status;
206                     if (mState == STATE_NORMAL_QUALITY) {
207                         Log.d(mTag, "keeps normal quality.");
208                         mMediaQualityStatus = status;
209                         return;
210                     } else {
211                         // check normal quality is stable or not.
212                         mHysteresisTimerId = mQnsTimer.registerTimer(
213                                 Message.obtain(this, EVENT_HYSTERESIS_FOR_NORMAL_QUALITY),
214                                 HYSTERESIS_TIME_NORMAL_QUALITY_MILLIS);
215                     }
216                 } else {
217                     // Threshold breached.
218                     mQnsTimer.unregisterTimer(mHysteresisTimerId);
219                     mHysteresisTimerId = INVALID_ID;
220                     switch (mState) {
221                         case STATE_NORMAL_QUALITY:
222                         case STATE_SUSPECT_LOW_QUALITY:
223                             if (reason == (1 << QnsConstants.RTP_LOW_QUALITY_REASON_PACKET_LOSS)) {
224                                 int delayMillis = (mConfigManager.getRTPMetricsData()).mPktLossTime;
225                                 if (delayMillis > 0) {
226                                     if (mState == STATE_NORMAL_QUALITY) {
227                                         enterSuspectLowQualityState(delayMillis);
228                                     }
229                                 } else if (delayMillis == 0) {
230                                     needNotify = true;
231                                 }
232                             } else {
233                                 mQnsTimer.unregisterTimer(mPacketLossTimerId);
234                                 mPacketLossTimerId = INVALID_ID;
235                                 enterLowQualityState(status);
236                                 needNotify = true;
237                             }
238                             break;
239 
240                         case STATE_LOW_QUALITY:
241                             if (mMediaQualityStatus.getTransportType() == status.getTransportType()
242                                     && thresholdBreached(mMediaQualityStatus)
243                                     != thresholdBreached(status)) {
244                                 needNotify = true;
245                             }
246                             break;
247                     }
248                     mMediaQualityStatus = status;
249                 }
250                 if (needNotify) {
251                     enterLowQualityState(status);
252                     notifyLowMediaQuality(reason);
253                 }
254 
255             }
256 
257             @VisibleForTesting
enterLowQualityState(MediaQualityStatus status)258             void enterLowQualityState(MediaQualityStatus status) {
259                 Log.d(mTag, "enterLowQualityState " + status);
260                 mState = STATE_LOW_QUALITY;
261                 mPollingCheckTimerId = mQnsTimer.registerTimer(
262                         Message.obtain(this, EVENT_POLLING_CHECK_LOW_QUALITY),
263                         LOW_QUALITY_CHECK_INTERVAL_MILLIS);
264             }
265 
enterSuspectLowQualityState(int delayMillis)266             void enterSuspectLowQualityState(int delayMillis) {
267                 Log.d(mTag, "enterSuspectLowQualityState.");
268                 mQnsTimer.unregisterTimer(mPacketLossTimerId);
269                 Log.d(mTag, "Packet loss timer start. " + delayMillis);
270                 Message msg = this.obtainMessage(
271                         EVENT_PACKET_LOSS_TIMER_EXPIRED, mTransportType, 0);
272                 mPacketLossTimerId = mQnsTimer.registerTimer(msg, delayMillis);
273                 mState = STATE_SUSPECT_LOW_QUALITY;
274             }
275 
exitLowQualityState()276             void exitLowQualityState() {
277                 mState = STATE_NORMAL_QUALITY;
278                 this.removeCallbacksAndMessages(null);
279                 mQnsTimer.unregisterTimer(mPacketLossTimerId);
280                 mQnsTimer.unregisterTimer(mHysteresisTimerId);
281                 mQnsTimer.unregisterTimer(mPollingCheckTimerId);
282                 mPacketLossTimerId = INVALID_ID;
283                 mHysteresisTimerId = INVALID_ID;
284                 mPollingCheckTimerId = INVALID_ID;
285                 notifyLowMediaQuality(0);
286             }
287 
checkLowQuality()288             void checkLowQuality() {
289                 if (mState == STATE_NORMAL_QUALITY) {
290                     Log.w(mTag, "checkLowQuality on unexpected state(normal state).");
291                 } else {
292                     Log.d(mTag, "checkLowQuality");
293                     int reason = thresholdBreached(mMediaQualityStatus);
294                     if (reason > 0) {
295                         notifyLowMediaQuality(thresholdBreached(mMediaQualityStatus));
296                     } else if (mHysteresisTimerId != INVALID_ID) {
297                         // hysteresis time to be normal state is running. let's check after that.
298                         mPollingCheckTimerId = mQnsTimer.registerTimer(
299                                 Message.obtain(this, EVENT_POLLING_CHECK_LOW_QUALITY),
300                                 HYSTERESIS_TIME_NORMAL_QUALITY_MILLIS);
301                     } else {
302                         Log.w(mTag, "Unexpected case.");
303                     }
304                 }
305             }
306 
updateForHandover(int transportType)307             void updateForHandover(int transportType) {
308                 // restart timers that they need to be restarted on new transport type.
309                 if (mState == STATE_SUSPECT_LOW_QUALITY) {
310                     mQnsTimer.unregisterTimer(mPacketLossTimerId);
311                     Message msg = this.obtainMessage(
312                             EVENT_PACKET_LOSS_TIMER_EXPIRED, transportType, 0);
313                     mPacketLossTimerId = mQnsTimer.registerTimer(msg,
314                             (mConfigManager.getRTPMetricsData()).mPktLossTime);
315                 }
316                 if (mHysteresisTimerId != INVALID_ID) {
317                     mQnsTimer.unregisterTimer(mHysteresisTimerId);
318                     mHysteresisTimerId = mQnsTimer.registerTimer(
319                             Message.obtain(this, EVENT_HYSTERESIS_FOR_NORMAL_QUALITY),
320                             HYSTERESIS_TIME_NORMAL_QUALITY_MILLIS);
321                 }
322                 if (mState == STATE_LOW_QUALITY) {
323                     mQnsTimer.unregisterTimer(mPollingCheckTimerId);
324                     mPollingCheckTimerId = mQnsTimer.registerTimer(
325                             Message.obtain(this, EVENT_POLLING_CHECK_LOW_QUALITY),
326                             LOW_QUALITY_CHECK_AFTER_HO_MILLIS);
327                 }
328             }
329 
onPacketLossTimerExpired(int transportType)330             private void onPacketLossTimerExpired(int transportType) {
331                 if (mTransportType != transportType) {
332                     Log.d(mTag, "onPacketLossTimerExpired transport type mismatched.");
333                     if (mState == STATE_SUSPECT_LOW_QUALITY) {
334                         mState = STATE_NORMAL_QUALITY;
335                     }
336                     return;
337                 }
338                 if (thresholdBreached(mMediaQualityStatus)
339                         == (1 << QnsConstants.RTP_LOW_QUALITY_REASON_PACKET_LOSS)) {
340                     enterLowQualityState(mMediaQualityStatus);
341                     notifyLowMediaQuality(1 << QnsConstants.RTP_LOW_QUALITY_REASON_PACKET_LOSS);
342                 }
343             }
344 
notifyLowMediaQuality(int reason)345             private void notifyLowMediaQuality(int reason) {
346                 long now = QnsUtils.getSystemElapsedRealTime();
347                 TransportQuality tq = getLastTransportQuality(mTransportType);
348                 if (tq != null) {
349                     if (reason > 0) {
350                         tq.mLowRtpQualityReportedTime = now;
351                     } else {
352                         tq.mLowRtpQualityReportedTime = LOW_QUALITY_REPORTED_TIME_INITIAL_VALUE;
353                     }
354                 }
355                 Log.d(mTag, "notifyLowMediaQuality reason:" + reason + " transport type:"
356                         + QnsConstants.transportTypeToString(mTransportType));
357                 mLowMediaQualityListeners.notifyResult(reason);
358             }
359         }
360 
361         class TransportQuality {
362             int mTransportType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
363             long mLowRtpQualityReportedTime =
364                     MediaLowQualityHandler.LOW_QUALITY_REPORTED_TIME_INITIAL_VALUE;
365             List<CallQualityBlock> mCallQualityBlockList;
366 
TransportQuality(int transportType)367             TransportQuality(int transportType) {
368                 mTransportType = transportType;
369                 mCallQualityBlockList = new ArrayList<>();
370             }
371 
isLowRtpQualityReported()372             boolean isLowRtpQualityReported() {
373                 return mLowRtpQualityReportedTime
374                         != MediaLowQualityHandler.LOW_QUALITY_REPORTED_TIME_INITIAL_VALUE;
375             }
376 
getLastCallQualityBlock()377             CallQualityBlock getLastCallQualityBlock() {
378                 int length = mCallQualityBlockList.size();
379                 if (length > 0) {
380                     return mCallQualityBlockList.get(length - 1);
381                 } else {
382                     return null;
383                 }
384             }
385         }
386 
ActiveCallTracker(int slotIndex, Looper looper)387         ActiveCallTracker(int slotIndex, Looper looper) {
388             mLogTag = ActiveCallTracker.class.getSimpleName() + "_" + slotIndex;
389             if (looper == null) {
390                 mHandlerThread = new HandlerThread(ActiveCallTracker.class.getSimpleName());
391                 mHandlerThread.start();
392                 mActiveCallHandler = new ActiveCallTrackerHandler(mHandlerThread.getLooper());
393                 mLowQualityHandler = new MediaLowQualityHandler(mHandlerThread.getLooper());
394             } else {
395                 mActiveCallHandler = new ActiveCallTrackerHandler(looper);
396                 mLowQualityHandler = new MediaLowQualityHandler(looper);
397             }
398             mTelephonyListener.addMediaQualityStatusCallback(mMediaQualityStatusConsumer);
399             mTransportQualityArray.put(
400                     AccessNetworkConstants.TRANSPORT_TYPE_WLAN, new ArrayList<>());
401             mTransportQualityArray.put(
402                     AccessNetworkConstants.TRANSPORT_TYPE_WWAN, new ArrayList<>());
403         }
404 
close()405         void close() {
406             mTelephonyListener.removeMediaQualityStatusCallback(mMediaQualityStatusConsumer);
407             if (mNetCapability != QnsConstants.INVALID_VALUE) {
408                 mTelephonyListener.unregisterPreciseDataConnectionStateChanged(
409                         mNetCapability, mActiveCallHandler);
410                 mNetCapability = QnsConstants.INVALID_VALUE;
411             }
412             if (mHandlerThread != null) {
413                 mHandlerThread.quitSafely();
414             }
415         }
416 
417         @VisibleForTesting
onDataConnectionStatusChanged(PreciseDataConnectionState state)418         void onDataConnectionStatusChanged(PreciseDataConnectionState state) {
419             if (state == null) {
420                 Log.d(mLogTag, "onDataConnectionStatusChanged with null info");
421                 return;
422             }
423             if (state.getState() == TelephonyManager.DATA_CONNECTED) {
424                 int transportType = state.getTransportType();
425                 if (transportType == AccessNetworkConstants.TRANSPORT_TYPE_INVALID) {
426                     Log.w(mLogTag, "Unexpected transport type on connected DataNetwork.");
427                     return;
428                 }
429                 if (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_INVALID) {
430                     Log.d(mLogTag, "Call started with "
431                             + QnsConstants.transportTypeToString(transportType));
432                     mTransportType = transportType;
433                     startTrackingTransportQuality(transportType);
434                 } else if (mTransportType != transportType) {
435                     Log.d(mLogTag, "Call Handed over to "
436                             + QnsConstants.transportTypeToString(transportType));
437                     mTransportType = transportType;
438                     onHandoverCompleted(transportType);
439                 }
440             }
441         }
442 
onHandoverCompleted( @ccessNetworkConstants.TransportType int dstTransportType)443         private void onHandoverCompleted(
444                 @AccessNetworkConstants.TransportType int dstTransportType) {
445             long now = QnsUtils.getSystemElapsedRealTime();
446             // complete to update TransportQuality for prev transport type
447             CallQualityBlock last = null;
448             int prevTransportType = QnsUtils.getOtherTransportType(dstTransportType);
449             TransportQuality prev = getLastTransportQuality(prevTransportType);
450             if (prev != null) {
451                 last = prev.getLastCallQualityBlock();
452             }
453             // add a new TransportQuality for new transport type
454             mTransportQualityArray.get(dstTransportType)
455                     .add(new TransportQuality(dstTransportType));
456             TransportQuality current = getLastTransportQuality(dstTransportType);
457             if (last != null) {
458                 last.mDurationMillis = now - last.mCreatedElapsedTime;
459                 current.mCallQualityBlockList
460                         .add(new CallQualityBlock(last.mUpLinkLevel, last.mDownLinkLevel, now));
461             }
462             mLowQualityHandler.updateForHandover(dstTransportType);
463         }
464 
startTrackingTransportQuality(int transportType)465         private void startTrackingTransportQuality(int transportType) {
466             mTransportQualityArray.get(AccessNetworkConstants.TRANSPORT_TYPE_WLAN).clear();
467             mTransportQualityArray.get(AccessNetworkConstants.TRANSPORT_TYPE_WWAN).clear();
468             mTransportQualityArray.get(transportType)
469                     .add(new TransportQuality(transportType));
470         }
471 
callStarted(@nsConstants.QnsCallType int callType, int netCapability)472         void callStarted(@QnsConstants.QnsCallType int callType, int netCapability) {
473             if (mCallType != QnsConstants.CALL_TYPE_IDLE) {
474                 if (mCallType != callType) {
475                     callTypeUpdated(callType);
476                 } else {
477                     Log.w(mLogTag, "call type:" + callType + " already started.");
478                 }
479             }
480             Log.d(mLogTag, "callStarted callType: " + callType + " netCapa:"
481                     + QnsUtils.getNameOfNetCapability(netCapability));
482             mCallType = callType;
483             mNetCapability = netCapability;
484             //Transport type will be updated when EVENT_DATA_CONNECTION_STATUS_CHANGED occurs.
485             PreciseDataConnectionState dataState =
486                     mTelephonyListener.getLastPreciseDataConnectionState(netCapability);
487             if (dataState != null && dataState.getTransportType()
488                     != AccessNetworkConstants.TRANSPORT_TYPE_INVALID) {
489                 mTransportType = dataState.getTransportType();
490                 startTrackingTransportQuality(mTransportType);
491             }
492             mTelephonyListener.registerPreciseDataConnectionStateChanged(mNetCapability,
493                     mActiveCallHandler, EVENT_DATA_CONNECTION_STATUS_CHANGED, null, true);
494         }
495 
callTypeUpdated(@nsConstants.QnsCallType int callType)496         private void callTypeUpdated(@QnsConstants.QnsCallType int callType) {
497             Log.d(mLogTag, "callTypeUpdated from " + mCallType + " to " + callType);
498             mCallType = callType;
499         }
500 
callEnded()501         void callEnded() {
502             mLowQualityHandler.exitLowQualityState();
503             long now = QnsUtils.getSystemElapsedRealTime();
504             // complete to update TransportQuality for prev transport type
505             CallQualityBlock last = null;
506             TransportQuality prev = getLastTransportQuality(mTransportType);
507             if (prev != null) {
508                 last = prev.getLastCallQualityBlock();
509             }
510             if (last != null) {
511                 last.mDurationMillis = now - last.mCreatedElapsedTime;
512             }
513             long upLinkQualityOverWwan = mActiveCallTracker
514                     .getUpLinkQualityLevelDuringCall(AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
515             long upLinkQualityOverWlan = mActiveCallTracker
516                     .getUpLinkQualityLevelDuringCall(AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
517             long downLinkQualityOverWwan = mActiveCallTracker
518                     .getDownLinkQualityLevelDuringCall(AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
519             long downLinkQualityOverWlan = mActiveCallTracker
520                     .getDownLinkQualityLevelDuringCall(AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
521             StringBuilder sb = new StringBuilder();
522             sb.append("CallQuality [WWAN:");
523             if (upLinkQualityOverWwan == QnsConstants.INVALID_VALUE
524                     || downLinkQualityOverWwan == QnsConstants.INVALID_VALUE) {
525                 sb.append("Not available] ");
526             } else {
527                 sb.append("upLinkQualityOverWwan = ").append(upLinkQualityOverWwan)
528                         .append(", downLinkQualityOverWwan = ").append(downLinkQualityOverWwan)
529                         .append("] ");
530             }
531             sb.append("[WLAN:");
532             if (upLinkQualityOverWlan == QnsConstants.INVALID_VALUE
533                     || downLinkQualityOverWlan == QnsConstants.INVALID_VALUE) {
534                 sb.append("Not available] ");
535             } else {
536                 sb.append("upLinkQualityOverWlan = ").append(upLinkQualityOverWwan)
537                         .append(", downLinkQualityOverWlan = ").append(downLinkQualityOverWwan)
538                         .append("] ");
539             }
540             Log.d(mLogTag, "callEnded callType: " + mCallType + " netCapa:"
541                     + QnsUtils.getNameOfNetCapability(mNetCapability) + " " + sb.toString());
542             mCallType = QnsConstants.CALL_TYPE_IDLE;
543             mTelephonyListener.unregisterPreciseDataConnectionStateChanged(
544                     mNetCapability, mActiveCallHandler);
545             mNetCapability = QnsConstants.INVALID_VALUE;
546             mAccessNetwork = AccessNetworkConstants.AccessNetworkType.UNKNOWN;
547             mTransportType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
548         }
549 
onMediaQualityStatusChanged(MediaQualityStatus status)550         void onMediaQualityStatusChanged(MediaQualityStatus status) {
551             if (status == null) {
552                 Log.e(mLogTag, "null MediaQualityStatus received.");
553                 return;
554             }
555             Message msg = mLowQualityHandler
556                     .obtainMessage(MediaLowQualityHandler.EVENT_MEDIA_QUALITY_CHANGED, status);
557             mLowQualityHandler.sendMessage(msg);
558         }
559 
getTransportType()560         int getTransportType() {
561             return this.mTransportType;
562         }
563 
getCallType()564         int getCallType() {
565             return this.mCallType;
566         }
567 
getNetCapability()568         int getNetCapability() {
569             return this.mNetCapability;
570         }
571 
572         @VisibleForTesting
getLastTransportQuality(int transportType)573         TransportQuality getLastTransportQuality(int transportType) {
574             if (transportType == AccessNetworkConstants.TRANSPORT_TYPE_INVALID) {
575                 Log.w(mLogTag, "getLastTransportQuality with invalid transport type.");
576                 return null;
577             }
578             int size = mTransportQualityArray.get(transportType).size();
579             if (size > 0) {
580                 return mTransportQualityArray.get(transportType).get(size - 1);
581             } else {
582                 return null;
583             }
584         }
585 
586         @VisibleForTesting
getTransportQualityList(int transportType)587         List<TransportQuality> getTransportQualityList(int transportType) {
588             return mTransportQualityArray.get(transportType);
589         }
590 
getUpLinkQualityLevelDuringCall(int transportType)591         long getUpLinkQualityLevelDuringCall(int transportType) {
592             List<TransportQuality> tqList = getTransportQualityList(transportType);
593             long sumUplinkQualityLevelVolume = 0;
594             long totalDuration = 0;
595             for (int i = 0; i < tqList.size(); i++) {
596                 List<CallQualityBlock> callQualityBlockList = tqList.get(i).mCallQualityBlockList;
597                 for (int j = 0; j < callQualityBlockList.size(); j++) {
598                     CallQualityBlock cq = callQualityBlockList.get(j);
599                     sumUplinkQualityLevelVolume += cq.getUpLinkQualityVolume();
600                     long durationMillis = cq.mDurationMillis;
601                     if (i == tqList.size() - 1 && j == callQualityBlockList.size() - 1) {
602                         if (durationMillis == 0) {
603                             durationMillis = QnsUtils.getSystemElapsedRealTime()
604                                     - cq.mCreatedElapsedTime;
605                         }
606                     }
607                     if (durationMillis > 0) {
608                         totalDuration += durationMillis;
609                     } else {
610                         return -1;
611                     }
612                 }
613             }
614             if (totalDuration <= 0) {
615                 return QnsConstants.INVALID_VALUE;
616             }
617             long qualityLevel = sumUplinkQualityLevelVolume / totalDuration;
618             Log.d(mLogTag, "getUplinkQualityLevel for [" + QnsConstants
619                     .transportTypeToString(transportType) + "] totalQualityVolume: "
620                     + sumUplinkQualityLevelVolume + ", totalDuration: " + totalDuration
621                     + " level:" + qualityLevel);
622             return qualityLevel;
623         }
624 
getDownLinkQualityLevelDuringCall(int transportType)625         long getDownLinkQualityLevelDuringCall(int transportType) {
626             List<TransportQuality> tqList = getTransportQualityList(transportType);
627             long sumDownLinkQualityLevelVolume = 0;
628             long totalDuration = 0;
629             for (int i = 0; i < tqList.size(); i++) {
630                 List<CallQualityBlock> callQualityBlockList = tqList.get(i).mCallQualityBlockList;
631                 for (int j = 0; j < callQualityBlockList.size(); j++) {
632                     CallQualityBlock cq = callQualityBlockList.get(j);
633                     sumDownLinkQualityLevelVolume += cq.getDownLinkQualityVolume();
634                     long durationMillis = cq.mDurationMillis;
635                     if (i == tqList.size() - 1 && j == callQualityBlockList.size() - 1) {
636                         if (durationMillis == 0) {
637                             durationMillis = QnsUtils.getSystemElapsedRealTime()
638                                     - cq.mCreatedElapsedTime;
639                         }
640                     }
641                     if (durationMillis > 0) {
642                         totalDuration += durationMillis;
643                     } else {
644                         return QnsConstants.INVALID_VALUE;
645                     }
646                 }
647             }
648             if (totalDuration <= 0) {
649                 return QnsConstants.INVALID_VALUE;
650             }
651             long qualityLevel = sumDownLinkQualityLevelVolume / totalDuration;
652             Log.d(mLogTag, "getDownLinkQualityLevel for [" + QnsConstants
653                     .transportTypeToString(transportType) + "] totalQualityVolume: "
654                     + sumDownLinkQualityLevelVolume + ", totalDuration: " + totalDuration
655                     + " level:" + qualityLevel);
656             return qualityLevel;
657         }
658 
updateCallQuality(CallState state)659         void updateCallQuality(CallState state) {
660             if (state == null) {
661                 Log.w(mLogTag, "updateCallQuality Null CallState.");
662                 return;
663             }
664             CallQuality cq = state.getCallQuality();
665             if (cq == null || isDummyCallQuality(cq)) {
666                 return;
667             }
668             mActiveCallHandler.post(() -> onUpdateCallQuality(cq));
669         }
670 
onUpdateCallQuality(CallQuality cq)671         private void onUpdateCallQuality(CallQuality cq) {
672             TransportQuality transportQuality = getLastTransportQuality(mTransportType);
673             if (transportQuality != null) {
674                 long now = QnsUtils.getSystemElapsedRealTime();
675                 CallQualityBlock prev = transportQuality.getLastCallQualityBlock();
676                 if (prev != null) {
677                     prev.mDurationMillis = now - prev.mCreatedElapsedTime;
678                 }
679                 transportQuality.mCallQualityBlockList.add(
680                         new CallQualityBlock(
681                                 cq.getUplinkCallQualityLevel(), cq.getDownlinkCallQualityLevel(),
682                                 now));
683             }
684         }
685 
isDummyCallQuality(CallQuality cq)686         private boolean isDummyCallQuality(CallQuality cq) {
687             return (cq.getNumRtpPacketsTransmitted() == 0
688                     && cq.getNumRtpPacketsReceived() == 0
689                     && cq.getUplinkCallQualityLevel() == 0
690                     && cq.getDownlinkCallQualityLevel() == 0);
691         }
692         /**
693          * Register an event for low media quality report.
694          *
695          * @param h the Handler to get event.
696          * @param what the event.
697          * @param userObj user object.
698          */
registerLowMediaQualityListener( Handler h, int what, Object userObj)699         void registerLowMediaQualityListener(
700                 Handler h, int what, Object userObj) {
701             Log.d(mLogTag, "registerLowMediaQualityListener");
702             if (h != null) {
703                 QnsRegistrant r = new QnsRegistrant(h, what, userObj);
704                 mLowMediaQualityListeners.add(r);
705             }
706         }
707 
708         /**
709          * Unregister an event for low media quality report.
710          *
711          * @param h the handler to get event.
712          */
unregisterLowMediaQualityListener(Handler h)713         void unregisterLowMediaQualityListener(Handler h) {
714             if (h != null) {
715                 mLowMediaQualityListeners.remove(h);
716             }
717         }
718 
719         @VisibleForTesting
thresholdBreached(MediaQualityStatus status)720         int thresholdBreached(MediaQualityStatus status) {
721             int breachedReason = 0;
722             QnsCarrierConfigManager.RtpMetricsConfig rtpConfig = mConfigManager.getRTPMetricsData();
723             if (status.getRtpPacketLossRate() > 0
724                     && status.getRtpPacketLossRate() >= rtpConfig.mPktLossRate) {
725                 breachedReason |= 1 << QnsConstants.RTP_LOW_QUALITY_REASON_PACKET_LOSS;
726             }
727             if (status.getRtpJitterMillis() > 0
728                     && status.getRtpJitterMillis() >= rtpConfig.mJitter) {
729                 breachedReason |= 1 << QnsConstants.RTP_LOW_QUALITY_REASON_JITTER;
730             }
731             if (status.getRtpInactivityMillis() > 0
732                     && status.getRtpInactivityMillis() >= rtpConfig.mNoRtpInterval) {
733                 breachedReason |= 1 << QnsConstants.RTP_LOW_QUALITY_REASON_NO_RTP;
734             }
735             return breachedReason;
736         }
737 
worseThanBefore(MediaQualityStatus before, MediaQualityStatus now)738         boolean worseThanBefore(MediaQualityStatus before, MediaQualityStatus now) {
739             return thresholdBreached(now) > thresholdBreached(before);
740         }
741     }
742 
QnsCallStatusTracker(QnsTelephonyListener telephonyListener, QnsCarrierConfigManager configManager, QnsTimer qnsTimer, int slotIndex)743     QnsCallStatusTracker(QnsTelephonyListener telephonyListener,
744             QnsCarrierConfigManager configManager, QnsTimer qnsTimer, int slotIndex) {
745         this(telephonyListener, configManager, qnsTimer, slotIndex, null);
746     }
747 
748     /** Only for test */
749     @VisibleForTesting
QnsCallStatusTracker(QnsTelephonyListener telephonyListener, QnsCarrierConfigManager configManager, QnsTimer qnsTimer, int slotIndex, Looper looper)750     QnsCallStatusTracker(QnsTelephonyListener telephonyListener,
751             QnsCarrierConfigManager configManager, QnsTimer qnsTimer, int slotIndex,
752             Looper looper) {
753         mLogTag = QnsCallStatusTracker.class.getSimpleName() + "_" + slotIndex;
754         mTelephonyListener = telephonyListener;
755         mConfigManager = configManager;
756         mQnsTimer = qnsTimer;
757         mActiveCallTracker = new ActiveCallTracker(slotIndex, looper);
758         mTelephonyListener.addCallStatesChangedCallback(mCallStatesConsumer);
759         mTelephonyListener.addSrvccStateChangedCallback(mSrvccStateConsumer);
760     }
761 
close()762     void close() {
763         mTelephonyListener.removeCallStatesChangedCallback(mCallStatesConsumer);
764         mTelephonyListener.removeSrvccStateChangedCallback(mSrvccStateConsumer);
765         if (mActiveCallTracker != null) {
766             mActiveCallTracker.close();
767         }
768     }
769 
updateCallState(List<CallState> callStateList)770     void updateCallState(List<CallState> callStateList) {
771         List<CallState> imsCallStateList = new ArrayList<>();
772         StringBuilder sb = new StringBuilder("");
773 
774         if (callStateList.size() > 0) {
775             for (CallState cs : callStateList) {
776                 if (cs.getImsCallServiceType() != ImsCallProfile.SERVICE_TYPE_NONE
777                         || cs.getImsCallType() != ImsCallProfile.CALL_TYPE_NONE) {
778                     if (cs.getCallState() != PreciseCallState.PRECISE_CALL_STATE_DISCONNECTED) {
779                         imsCallStateList.add(cs);
780                         sb.append("{" + cs + "}");
781                     }
782                 }
783             }
784         }
785         int ongoingCallNum = imsCallStateList.size();
786         mCallStates = imsCallStateList;
787         Log.d(mLogTag, "updateCallState callNum:(" + ongoingCallNum + "): [" + sb + "]");
788         if (imsCallStateList.size() == 0) {
789             if (mLastNormalCallType != QnsConstants.CALL_TYPE_IDLE) {
790                 mLastNormalCallType = QnsConstants.CALL_TYPE_IDLE;
791                 notifyCallType(NetworkCapabilities.NET_CAPABILITY_IMS, mLastNormalCallType);
792             }
793             if (mLastEmergencyCallType != QnsConstants.CALL_TYPE_IDLE) {
794                 mLastEmergencyCallType = QnsConstants.CALL_TYPE_IDLE;
795                 if (mEmergencyOverIms) {
796                     mEmergencyOverIms = false;
797                     notifyCallType(NetworkCapabilities.NET_CAPABILITY_IMS, mLastEmergencyCallType);
798                 } else {
799                     notifyCallType(NetworkCapabilities.NET_CAPABILITY_EIMS, mLastEmergencyCallType);
800                 }
801             }
802         } else {
803             //1. Notify Call Type IDLE, if the call was removed from the call list.
804             if (mLastNormalCallType != QnsConstants.CALL_TYPE_IDLE
805                     && !hasVideoCall() && !hasVoiceCall()) {
806                 mLastNormalCallType = QnsConstants.CALL_TYPE_IDLE;
807                 notifyCallType(NetworkCapabilities.NET_CAPABILITY_IMS, mLastNormalCallType);
808 
809             }
810             if (mLastEmergencyCallType != QnsConstants.CALL_TYPE_IDLE && !hasEmergencyCall()) {
811                 mLastEmergencyCallType = QnsConstants.CALL_TYPE_IDLE;
812                 if (mEmergencyOverIms) {
813                     mEmergencyOverIms = false;
814                     notifyCallType(NetworkCapabilities.NET_CAPABILITY_IMS, mLastEmergencyCallType);
815                 } else {
816                     notifyCallType(NetworkCapabilities.NET_CAPABILITY_EIMS, mLastEmergencyCallType);
817                 }
818             }
819             //2. Notify a new ongoing call type
820             if (hasEmergencyCall()) {
821                 if (mLastEmergencyCallType != QnsConstants.CALL_TYPE_EMERGENCY) {
822                     mLastEmergencyCallType = QnsConstants.CALL_TYPE_EMERGENCY;
823                     if (!isDataNetworkConnected(NetworkCapabilities.NET_CAPABILITY_EIMS)
824                             && isDataNetworkConnected(NetworkCapabilities.NET_CAPABILITY_IMS)) {
825                         notifyCallType(NetworkCapabilities.NET_CAPABILITY_IMS,
826                                 mLastEmergencyCallType);
827                         mEmergencyOverIms = true;
828                     } else {
829                         notifyCallType(NetworkCapabilities.NET_CAPABILITY_EIMS,
830                                 mLastEmergencyCallType);
831                     }
832                 }
833             } else if (hasVideoCall()) {
834                 if (mLastNormalCallType != QnsConstants.CALL_TYPE_VIDEO) {
835                     mLastNormalCallType = QnsConstants.CALL_TYPE_VIDEO;
836                     notifyCallType(NetworkCapabilities.NET_CAPABILITY_IMS, mLastNormalCallType);
837                 }
838             } else if (hasVoiceCall()) {
839                 if (mLastNormalCallType != QnsConstants.CALL_TYPE_VOICE) {
840                     mLastNormalCallType = QnsConstants.CALL_TYPE_VOICE;
841                     notifyCallType(NetworkCapabilities.NET_CAPABILITY_IMS, mLastNormalCallType);
842                 }
843             }
844             if (mActiveCallTracker.getCallType() != QnsConstants.CALL_TYPE_IDLE) {
845                 mActiveCallTracker.updateCallQuality(getActiveCall());
846             }
847         }
848     }
849 
notifyCallType(int netCapability, int callType)850     private void notifyCallType(int netCapability, int callType) {
851         Log.d(mLogTag, "notifyCallType for " + QnsUtils.getNameOfNetCapability(netCapability)
852                 + ", callType:" + callType);
853         if (netCapability == NetworkCapabilities.NET_CAPABILITY_IMS
854                 && mCallTypeChangedEventListener != null) {
855             mCallTypeChangedEventListener.notifyResult(callType);
856         } else if (netCapability == NetworkCapabilities.NET_CAPABILITY_EIMS
857                 && mEmergencyCallTypeChangedEventListener != null) {
858             mEmergencyCallTypeChangedEventListener.notifyResult(callType);
859         }
860         if (callType == QnsConstants.CALL_TYPE_IDLE) {
861             mActiveCallTracker.callEnded();
862         } else {
863             mActiveCallTracker.callStarted(callType, netCapability);
864         }
865         mQnsTimer.updateCallState(callType);
866     }
867 
isCallIdle()868     boolean isCallIdle() {
869         return mCallStates.size() == 0;
870     }
871 
isCallIdle(int netCapability)872     boolean isCallIdle(int netCapability) {
873         int callNum = mCallStates.size();
874         if (callNum == 0) {
875             return true;
876         }
877         if (netCapability == NetworkCapabilities.NET_CAPABILITY_IMS) {
878             return (mLastNormalCallType == QnsConstants.CALL_TYPE_IDLE)
879                     && (mLastEmergencyCallType != QnsConstants.CALL_TYPE_IDLE
880                     && !mEmergencyOverIms);
881         } else if (netCapability == NetworkCapabilities.NET_CAPABILITY_EIMS) {
882             return mLastEmergencyCallType == QnsConstants.CALL_TYPE_IDLE || mEmergencyOverIms;
883         }
884         return false;
885     }
886 
hasEmergencyCall()887     boolean hasEmergencyCall() {
888         for (CallState cs : mCallStates) {
889             if (cs.getImsCallServiceType() == ImsCallProfile.SERVICE_TYPE_EMERGENCY
890                     && cs.getCallState() == PreciseCallState.PRECISE_CALL_STATE_ACTIVE) {
891                 return true;
892             }
893         }
894         return false;
895     }
896 
getActiveCall()897     CallState getActiveCall() {
898         for (CallState cs : mCallStates) {
899             if (cs.getCallState() == PreciseCallState.PRECISE_CALL_STATE_ACTIVE) {
900                 return cs;
901             }
902         }
903         return null;
904     }
905 
hasVideoCall()906     boolean hasVideoCall() {
907         for (CallState cs : mCallStates) {
908             if (cs.getImsCallServiceType() == ImsCallProfile.SERVICE_TYPE_NORMAL
909                     && cs.getImsCallType() == ImsCallProfile.CALL_TYPE_VT
910                     && (cs.getCallState() == PreciseCallState.PRECISE_CALL_STATE_DISCONNECTING
911                             || cs.getCallState() == PreciseCallState.PRECISE_CALL_STATE_HOLDING
912                             || cs.getCallState() == PreciseCallState.PRECISE_CALL_STATE_ACTIVE)) {
913                 return true;
914             }
915         }
916         return false;
917     }
918 
hasVoiceCall()919     boolean hasVoiceCall() {
920         for (CallState cs : mCallStates) {
921             if (cs.getImsCallServiceType() == ImsCallProfile.SERVICE_TYPE_NORMAL
922                     && cs.getImsCallType() == ImsCallProfile.CALL_TYPE_VOICE) {
923                 return true;
924             }
925         }
926         return false;
927     }
928 
929     /**
930      * register call type changed event.
931      *
932      * @param netCapability Network Capability of caller
933      * @param h Handler want to receive event.
934      * @param what event Id to receive
935      * @param userObj user object
936      */
registerCallTypeChangedListener( int netCapability, @NonNull Handler h, int what, Object userObj)937     void registerCallTypeChangedListener(
938             int netCapability, @NonNull Handler h, int what, Object userObj) {
939         if (netCapability != NetworkCapabilities.NET_CAPABILITY_IMS
940                 && netCapability != NetworkCapabilities.NET_CAPABILITY_EIMS) {
941             Log.d(mLogTag, "registerCallTypeChangedListener : wrong netCapability");
942             return;
943         }
944         if (h != null) {
945             QnsRegistrant r = new QnsRegistrant(h, what, userObj);
946             if (netCapability == NetworkCapabilities.NET_CAPABILITY_IMS) {
947                 mCallTypeChangedEventListener = r;
948             } else if (netCapability == NetworkCapabilities.NET_CAPABILITY_EIMS) {
949                 mEmergencyCallTypeChangedEventListener = r;
950             }
951         } else {
952             Log.d(mLogTag, "registerCallTypeChangedListener : Handler is Null");
953         }
954     }
955 
956     /**
957      * Unregister call type changed event.
958      *
959      * @param netCapability Network Capability of caller
960      * @param h Handler want to receive event.
961      */
unregisterCallTypeChangedListener(int netCapability, @NonNull Handler h)962     void unregisterCallTypeChangedListener(int netCapability, @NonNull Handler h) {
963         if (netCapability != NetworkCapabilities.NET_CAPABILITY_IMS
964                 && netCapability != NetworkCapabilities.NET_CAPABILITY_EIMS) {
965             Log.d(mLogTag, "unregisterCallTypeChangedListener : wrong netCapability");
966             return;
967         }
968         if (h != null) {
969             if (netCapability == NetworkCapabilities.NET_CAPABILITY_IMS) {
970                 mCallTypeChangedEventListener = null;
971             } else if (netCapability == NetworkCapabilities.NET_CAPABILITY_EIMS) {
972                 mEmergencyCallTypeChangedEventListener = null;
973             }
974         } else {
975             Log.d(mLogTag, "unregisterCallTypeChangedListener : Handler is Null");
976         }
977     }
978 
getActiveCallTracker()979     ActiveCallTracker getActiveCallTracker() {
980         return mActiveCallTracker;
981     }
982 
983     @VisibleForTesting
onSrvccStateChangedInternal(int srvccState)984     void onSrvccStateChangedInternal(int srvccState) {
985         if (srvccState == TelephonyManager.SRVCC_STATE_HANDOVER_COMPLETED) {
986             mCallStates.clear();
987             if (mLastNormalCallType != QnsConstants.CALL_TYPE_IDLE) {
988                 mLastNormalCallType = QnsConstants.CALL_TYPE_IDLE;
989                 if (mCallTypeChangedEventListener != null) {
990                     mCallTypeChangedEventListener.notifyResult(mLastNormalCallType);
991                 }
992             }
993             if (mLastEmergencyCallType != QnsConstants.CALL_TYPE_IDLE) {
994                 mLastEmergencyCallType = QnsConstants.CALL_TYPE_IDLE;
995                 if (mEmergencyOverIms) {
996                     mEmergencyOverIms = false;
997                     if (mCallTypeChangedEventListener != null) {
998                         mCallTypeChangedEventListener.notifyResult(mLastEmergencyCallType);
999                     }
1000                 } else {
1001                     if (mEmergencyCallTypeChangedEventListener != null) {
1002                         mEmergencyCallTypeChangedEventListener.notifyResult(mLastEmergencyCallType);
1003                     }
1004                 }
1005             }
1006         }
1007     }
1008 
1009 
isDataNetworkConnected(int netCapability)1010     private boolean isDataNetworkConnected(int netCapability) {
1011         PreciseDataConnectionState preciseDataStatus =
1012                 mTelephonyListener.getLastPreciseDataConnectionState(netCapability);
1013 
1014         if (preciseDataStatus == null) return false;
1015         int state = preciseDataStatus.getState();
1016         return (state == TelephonyManager.DATA_CONNECTED
1017                 || state == TelephonyManager.DATA_HANDOVER_IN_PROGRESS
1018                 || state == TelephonyManager.DATA_SUSPENDED);
1019     }
1020 }
1021