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.imsmedia;
18 
19 import android.hardware.radio.ims.media.IImsMediaSession;
20 import android.os.Handler;
21 import android.os.Looper;
22 import android.os.Message;
23 import android.os.RemoteException;
24 import android.support.annotation.VisibleForTesting;
25 import android.telephony.CallQuality;
26 import android.telephony.ims.RtpHeaderExtension;
27 import android.telephony.imsmedia.AudioConfig;
28 import android.telephony.imsmedia.IImsAudioSession;
29 import android.telephony.imsmedia.IImsAudioSessionCallback;
30 import android.telephony.imsmedia.ImsMediaSession;
31 import android.telephony.imsmedia.MediaQualityStatus;
32 import android.telephony.imsmedia.MediaQualityThreshold;
33 import android.telephony.imsmedia.RtpConfig;
34 import android.telephony.imsmedia.RtpReceptionStats;
35 
36 import androidx.annotation.NonNull;
37 import androidx.annotation.Nullable;
38 
39 import com.android.telephony.imsmedia.Utils.OpenSessionParams;
40 import com.android.telephony.imsmedia.util.Log;
41 
42 import java.util.List;
43 import java.util.stream.Collectors;
44 
45 /**
46  * Audio session binder implementation which handles all audio session APIs
47  * from the VOIP applications.
48  */
49 public final class AudioSession extends IImsAudioSession.Stub implements IMediaSession {
50     private static final String TAG = "AudioSession";
51 
52     public static final int CMD_OPEN_SESSION = 101;
53     public static final int CMD_CLOSE_SESSION = 102;
54     public static final int CMD_MODIFY_SESSION = 103;
55     public static final int CMD_ADD_CONFIG = 104;
56     public static final int CMD_DELETE_CONFIG = 105;
57     public static final int CMD_CONFIRM_CONFIG = 106;
58     public static final int CMD_SEND_DTMF = 107;
59     public static final int CMD_SEND_RTP_HDR_EXTN = 108;
60     public static final int CMD_SET_MEDIA_QUALITY_THRESHOLD = 109;
61     public static final int CMD_START_DTMF = 110;
62     public static final int CMD_STOP_DTMF = 111;
63     public static final int CMD_REQUEST_RECEPTION_STATS = 112;
64     public static final int CMD_ADJUST_DELAY = 113;
65 
66     public static final int EVENT_OPEN_SESSION_SUCCESS = 201;
67     public static final int EVENT_OPEN_SESSION_FAILURE = 202;
68     public static final int EVENT_MODIFY_SESSION_RESPONSE = 203;
69     public static final int EVENT_ADD_CONFIG_RESPONSE = 204;
70     public static final int EVENT_CONFIRM_CONFIG_RESPONSE = 205;
71     public static final int EVENT_FIRST_MEDIA_PACKET_IND = 206;
72     public static final int EVENT_RTP_HEADER_EXTENSION_IND = 207;
73     public static final int EVENT_MEDIA_QUALITY_STATUS_IND = 208;
74     public static final int EVENT_TRIGGER_ANBR_QUERY_IND = 209;
75     public static final int EVENT_DTMF_RECEIVED_IND = 210;
76     public static final int EVENT_CALL_QUALITY_CHANGE_IND = 211;
77     public static final int EVENT_SESSION_CLOSED = 212;
78     public static final int EVENT_NOTIFY_RECEPTION_STATS = 213;
79 
80     private static final int DTMF_DEFAULT_DURATION = 140;
81 
82     private int mSessionId;
83     private AudioOffloadService mOffloadService;
84     private AudioOffloadListener mOffloadListener;
85     private IImsAudioSessionCallback mCallback;
86     private IImsMediaSession mHalSession;
87     private AudioSessionHandler mHandler;
88     private boolean mIsAudioOffload;
89     private AudioService mAudioService;
90     private AudioListener mAudioListener;
91     private AudioLocalSession mLocalSession;
92 
AudioSession(final int sessionId, final IImsAudioSessionCallback callback)93     AudioSession(final int sessionId, final IImsAudioSessionCallback callback) {
94         mSessionId = sessionId;
95         mCallback = callback;
96         mHandler = new AudioSessionHandler(Looper.getMainLooper());
97         if (isAudioOffload()) {
98             Log.d(TAG, "Initialize offload service");
99             mOffloadService = AudioOffloadService.getInstance();
100             mOffloadListener = new AudioOffloadListener(mHandler);
101         } else {
102             Log.d(TAG, "Initialize local audio service");
103             mAudioService = new AudioService();
104             mAudioListener = new AudioListener(mHandler);
105             mAudioService.setListener(mAudioListener);
106             mAudioListener.setNativeObject(mAudioService.getNativeObject());
107         }
108     }
109 
110     @VisibleForTesting
AudioSession(final int sessionId, @NonNull final IImsAudioSessionCallback callback, @Nullable final AudioService audioService, @Nullable final AudioLocalSession localSession, @Nullable final AudioOffloadService offloadService, Looper looper)111     AudioSession(final int sessionId,
112             @NonNull final IImsAudioSessionCallback callback,
113             @Nullable final AudioService audioService,
114             @Nullable final AudioLocalSession localSession,
115             @Nullable final AudioOffloadService offloadService,
116             Looper looper) {
117         mSessionId = sessionId;
118         mCallback = callback;
119         mHandler = new AudioSessionHandler(looper);
120         mAudioService = audioService;
121         mLocalSession = localSession;
122         mAudioListener = new AudioListener(mHandler);
123         mOffloadService = offloadService;
124         mOffloadListener = new AudioOffloadListener(mHandler);
125     }
126 
127     @VisibleForTesting
setAudioOffload(boolean isOffload)128     void setAudioOffload(boolean isOffload) {
129         mIsAudioOffload = isOffload;
130     }
131 
132     @VisibleForTesting
getAudioSessionHandler()133     AudioSessionHandler getAudioSessionHandler() {
134         return mHandler;
135     }
136 
137     @VisibleForTesting
getAudioListener()138     AudioListener getAudioListener() {
139         return mAudioListener;
140     }
141 
getOffloadListener()142     AudioOffloadListener getOffloadListener() {
143         return mOffloadListener;
144     }
145 
146     @Override
openSession(OpenSessionParams sessionParams)147     public void openSession(OpenSessionParams sessionParams) {
148         Utils.sendMessage(mHandler, CMD_OPEN_SESSION, sessionParams);
149         RtpConfig rtpConfig = sessionParams.getRtpConfig();
150         if (rtpConfig != null) {
151             WakeLockManager.getInstance().manageWakeLockOnMediaDirectionUpdate(
152                     mSessionId, rtpConfig.getMediaDirection());
153         }
154     }
155 
156     @Override
closeSession()157     public void closeSession() {
158         Utils.sendMessage(mHandler, CMD_CLOSE_SESSION);
159         WakeLockManager.getInstance().manageWakeLockOnMediaDirectionUpdate(
160                 mSessionId, RtpConfig.MEDIA_DIRECTION_NO_FLOW);
161     }
162 
163     @Override
getSessionId()164     public int getSessionId() {
165         return mSessionId;
166     }
167 
168     @Override
modifySession(AudioConfig config)169     public void modifySession(AudioConfig config) {
170         Log.d(TAG, "modifySession: " + Log.hidePii(String.valueOf(config)));
171         Utils.sendMessage(mHandler, CMD_MODIFY_SESSION, config);
172         WakeLockManager.getInstance().manageWakeLockOnMediaDirectionUpdate(
173                 mSessionId, config.getMediaDirection());
174     }
175 
176     @Override
addConfig(AudioConfig config)177     public void addConfig(AudioConfig config) {
178         Log.d(TAG, "addConfig: " + Log.hidePii(String.valueOf(config)));
179         Utils.sendMessage(mHandler, CMD_ADD_CONFIG, config);
180     }
181 
182     @Override
deleteConfig(AudioConfig config)183     public void deleteConfig(AudioConfig config) {
184         Log.d(TAG, "deleteConfig: " + Log.hidePii(String.valueOf(config)));
185         Utils.sendMessage(mHandler, CMD_DELETE_CONFIG, config);
186     }
187 
188     @Override
confirmConfig(AudioConfig config)189     public void confirmConfig(AudioConfig config) {
190         Log.d(TAG, "confirmConfig: " + Log.hidePii(String.valueOf(config)));
191         Utils.sendMessage(mHandler, CMD_CONFIRM_CONFIG, config);
192     }
193 
194     @Override
sendDtmf(char digit, int duration)195     public void sendDtmf(char digit, int duration) {
196         Log.dc(TAG, "sendDtmf: digit=" + digit + ",duration=" + duration);
197         Utils.sendMessage(mHandler, CMD_SEND_DTMF, duration, Utils.UNUSED, digit);
198     }
199 
200     @Override
startDtmf(char digit)201     public void startDtmf(char digit) {
202         Log.dc(TAG, "startDtmf: digit=" + digit);
203         Utils.sendMessage(mHandler, CMD_START_DTMF, digit);
204     }
205 
206     @Override
stopDtmf()207     public void stopDtmf() {
208         Log.dc(TAG, "stopDtmf");
209         Utils.sendMessage(mHandler, CMD_STOP_DTMF);
210     }
211     @Override
sendHeaderExtension(List<RtpHeaderExtension> extensions)212     public void sendHeaderExtension(List<RtpHeaderExtension> extensions) {
213         Log.d(TAG, "sendHeaderExtension" + Log.hidePii(String.valueOf(extensions)));
214         Utils.sendMessage(mHandler, CMD_SEND_RTP_HDR_EXTN, extensions);
215     }
216 
217     @Override
setMediaQualityThreshold(MediaQualityThreshold threshold)218     public void setMediaQualityThreshold(MediaQualityThreshold threshold) {
219         Log.d(TAG, "setMediaQualityThreshold: " + Log.hidePii(String.valueOf(threshold)));
220         Utils.sendMessage(mHandler, CMD_SET_MEDIA_QUALITY_THRESHOLD, threshold);
221     }
222 
223     @Override
requestRtpReceptionStats(int intervalMs)224     public void requestRtpReceptionStats(int intervalMs) {
225         Log.d(TAG, "requestRtpReceptionStats: interval=" + intervalMs);
226         Utils.sendMessage(mHandler, CMD_REQUEST_RECEPTION_STATS, intervalMs);
227     }
228 
229     @Override
adjustDelay(int delayMs)230     public void adjustDelay(int delayMs) {
231         Log.d(TAG, "adjustDelay: delay=" + delayMs);
232         Utils.sendMessage(mHandler, CMD_ADJUST_DELAY, delayMs);
233     }
234 
235     @Override
onOpenSessionSuccess(Object session)236     public void onOpenSessionSuccess(Object session) {
237         Utils.sendMessage(mHandler, EVENT_OPEN_SESSION_SUCCESS, session);
238     }
239 
240     @Override
onOpenSessionFailure(int error)241     public void onOpenSessionFailure(int error) {
242         Utils.sendMessage(mHandler, EVENT_OPEN_SESSION_FAILURE, error);
243     }
244 
245     @Override
onSessionClosed()246     public void onSessionClosed() {
247         Utils.sendMessage(mHandler, EVENT_SESSION_CLOSED);
248     }
249 
isAudioOffload()250     private boolean isAudioOffload() {
251         return mIsAudioOffload;
252     }
253 
254     /**
255      * Audio session message mHandler
256      */
257     class AudioSessionHandler extends Handler {
AudioSessionHandler(Looper looper)258         AudioSessionHandler(Looper looper) {
259             super(looper);
260         }
261 
262         @Override
handleMessage(Message msg)263         public void handleMessage (Message msg) {
264             Log.dc(TAG, "handleMessage() -" + AudioSessionHandler.this + ", " + msg.what);
265             switch(msg.what) {
266                 case CMD_OPEN_SESSION:
267                     handleOpenSession((OpenSessionParams)msg.obj);
268                     break;
269                 case CMD_CLOSE_SESSION:
270                     handleCloseSession();
271                     break;
272                 case CMD_MODIFY_SESSION:
273                     handleModifySession((AudioConfig)msg.obj);
274                     break;
275                 case CMD_ADD_CONFIG:
276                     handleAddConfig((AudioConfig)msg.obj);
277                     break;
278                 case CMD_DELETE_CONFIG:
279                     handleDeleteConfig((AudioConfig)msg.obj);
280                     break;
281                 case CMD_CONFIRM_CONFIG:
282                     handleConfirmConfig((AudioConfig)msg.obj);
283                     break;
284                 case CMD_SEND_DTMF:
285                     handleSendDtmf((char) msg.obj, msg.arg1);
286                     break;
287                 case CMD_START_DTMF:
288                     handleStartDtmf((char) msg.obj);
289                     break;
290                 case CMD_STOP_DTMF:
291                     handleStopDtmf();
292                     break;
293                 case CMD_SEND_RTP_HDR_EXTN:
294                     handleSendRtpHeaderExtension((List<RtpHeaderExtension>)msg.obj);
295                     break;
296                 case CMD_SET_MEDIA_QUALITY_THRESHOLD:
297                     handleSetMediaQualityThreshold((MediaQualityThreshold)msg.obj);
298                     break;
299                 case CMD_REQUEST_RECEPTION_STATS:
300                     handleRequestRtpReceptionStats((int) msg.obj);
301                     break;
302                 case CMD_ADJUST_DELAY:
303                     handleAdjustDelay((int) msg.obj);
304                     break;
305                 case EVENT_OPEN_SESSION_SUCCESS:
306                     handleOpenSuccess(msg.obj);
307                     break;
308                 case EVENT_OPEN_SESSION_FAILURE:
309                     handleOpenFailure((int)msg.obj);
310                     break;
311                 case EVENT_SESSION_CLOSED:
312                     handleSessionClosed();
313                     break;
314                 case EVENT_MODIFY_SESSION_RESPONSE:
315                     handleModifySessionRespose((AudioConfig)msg.obj, msg.arg1);
316                     break;
317                 case EVENT_ADD_CONFIG_RESPONSE:
318                     handleAddConfigResponse((AudioConfig)msg.obj, msg.arg1);
319                     break;
320                 case EVENT_CONFIRM_CONFIG_RESPONSE:
321                     handleConfirmConfigResponse((AudioConfig)msg.obj, msg.arg1);
322                     break;
323                 case EVENT_FIRST_MEDIA_PACKET_IND:
324                     handleFirstMediaPacketInd((AudioConfig)msg.obj);
325                     break;
326                 case EVENT_RTP_HEADER_EXTENSION_IND:
327                     handleRtpHeaderExtensionInd((List<RtpHeaderExtension>)msg.obj);
328                     break;
329                 case EVENT_MEDIA_QUALITY_STATUS_IND:
330                     handleNotifyMediaQualityStatus((MediaQualityStatus) msg.obj);
331                     break;
332                 case EVENT_TRIGGER_ANBR_QUERY_IND:
333                     handleTriggerAnbrQuery((AudioConfig) msg.obj);
334                     break;
335                 case EVENT_DTMF_RECEIVED_IND:
336                     handleDtmfReceived((char) msg.arg1, msg.arg2);
337                     break;
338                 case EVENT_CALL_QUALITY_CHANGE_IND:
339                     handleCallQualityChangeInd((CallQuality) msg.obj);
340                     break;
341                 case EVENT_NOTIFY_RECEPTION_STATS:
342                     handleNotifyReceptionStats((RtpReceptionStats) msg.obj);
343                     break;
344                 default:
345             }
346         }
347     }
348 
handleOpenSession(OpenSessionParams sessionParams)349     private void handleOpenSession(OpenSessionParams sessionParams) {
350         if (isAudioOffload()) {
351             mOffloadService.openSession(mSessionId, sessionParams);
352         } else {
353             mAudioListener.setMediaCallback(sessionParams.getCallback());
354             int result = mAudioService.openSession(mSessionId, sessionParams);
355             if (result != ImsMediaSession.RESULT_SUCCESS) {
356                 handleOpenFailure(result);
357             }
358         }
359     }
360 
handleCloseSession()361     private void handleCloseSession() {
362         if (isAudioOffload()) {
363             mOffloadService.closeSession(mSessionId);
364         } else {
365             mAudioService.closeSession(mSessionId);
366         }
367     }
368 
handleModifySession(AudioConfig config)369     private void handleModifySession(AudioConfig config) {
370         if (isAudioOffload()) {
371             try {
372                 mHalSession.modifySession(Utils.convertToRtpConfig(config));
373             } catch (RemoteException e) {
374                 Log.e(TAG, "modifySession : " + e);
375             }
376         } else {
377             mLocalSession.modifySession(config);
378         }
379     }
380 
handleAddConfig(AudioConfig config)381     private void handleAddConfig(AudioConfig config) {
382         if (isAudioOffload()) {
383             try {
384                 mHalSession.modifySession(Utils.convertToRtpConfig(config));
385             } catch (RemoteException e) {
386                 Log.e(TAG, "addConfig : " + e);
387             }
388         } else {
389             mLocalSession.addConfig(config);
390         }
391     }
392 
handleDeleteConfig(AudioConfig config)393     private void handleDeleteConfig(AudioConfig config) {
394         if (!isAudioOffload()) {
395             mLocalSession.deleteConfig(config);
396         }
397     }
398 
handleConfirmConfig(AudioConfig config)399     private void handleConfirmConfig(AudioConfig config) {
400         if (!isAudioOffload()) {
401             mLocalSession.confirmConfig(config);
402         }
403     }
404 
handleSendDtmf(char digit, int duration)405     private void handleSendDtmf(char digit, int duration) {
406         if (isAudioOffload()) {
407             try {
408                 mHalSession.sendDtmf(digit, duration);
409             } catch (RemoteException e) {
410                 Log.e(TAG, "sendDtmf : " + e);
411             }
412         } else {
413             mLocalSession.sendDtmf(digit, duration);
414         }
415     }
416 
handleStartDtmf(char digit)417     private void handleStartDtmf(char digit) {
418         if (isAudioOffload()) {
419             try {
420                 mHalSession.startDtmf(digit);
421             } catch (RemoteException e) {
422                 Log.e(TAG, "startDtmf : " + e);
423             }
424         } else {
425             mLocalSession.sendDtmf(digit, DTMF_DEFAULT_DURATION);
426         }
427     }
428 
handleStopDtmf()429     private void handleStopDtmf() {
430         if (isAudioOffload()) {
431             try {
432                 mHalSession.stopDtmf();
433             } catch (RemoteException e) {
434                 Log.e(TAG, "stopDtmf : " + e);
435             }
436         }
437     }
438 
handleSendRtpHeaderExtension(List<RtpHeaderExtension> extensions)439     private void handleSendRtpHeaderExtension(List<RtpHeaderExtension> extensions) {
440         if (isAudioOffload()) {
441             try {
442                 List<android.hardware.radio.ims.media.RtpHeaderExtension>
443                         halExtensions = extensions.stream().map(Utils::convertRtpHeaderExtension)
444                                 .collect(Collectors.toList());
445                 mHalSession.sendHeaderExtension(halExtensions);
446             } catch (RemoteException e) {
447                 Log.e(TAG, "sendHeaderExtension : " + e);
448             }
449         } else {
450             mLocalSession.sendHeaderExtension(extensions);
451         }
452     }
453 
handleSetMediaQualityThreshold(MediaQualityThreshold threshold)454     private void handleSetMediaQualityThreshold(MediaQualityThreshold threshold) {
455         if (isAudioOffload()) {
456             try {
457                 mHalSession.setMediaQualityThreshold(Utils.convertMediaQualityThreshold(threshold));
458             } catch (RemoteException e) {
459                 Log.e(TAG, "setMediaQualityThreshold: " + e);
460             }
461         } else {
462             mLocalSession.setMediaQualityThreshold(threshold);
463         }
464     }
465 
handleRequestRtpReceptionStats(int intervalMs)466     private void handleRequestRtpReceptionStats(int intervalMs) {
467         if (!isAudioOffload()) {
468             mLocalSession.requestRtpReceptionStats(intervalMs);
469         }
470     }
471 
handleAdjustDelay(int delayMs)472     private void handleAdjustDelay(int delayMs) {
473         if (!isAudioOffload()) {
474             mLocalSession.adjustDelay(delayMs);
475         }
476     }
477 
handleOpenSuccess(Object session)478     private void handleOpenSuccess(Object session) {
479        if (session instanceof IImsMediaSession) {
480             try {
481                 ((IImsMediaSession)session).setListener(mOffloadListener);
482             } catch (RemoteException e) {
483                 Log.e(TAG, "Failed to notify openSuccess: " + e);
484             }
485             mHalSession = (IImsMediaSession) session;
486         } else {
487             mLocalSession = (AudioLocalSession)session;
488         }
489 
490         try {
491             mCallback.onOpenSessionSuccess(this);
492         } catch (RemoteException e) {
493             Log.e(TAG, "Failed to notify openSuccess: " + e);
494         }
495     }
496 
handleOpenFailure(int error)497     private void handleOpenFailure(int error) {
498         try {
499             mCallback.onOpenSessionFailure(error);
500         }  catch (RemoteException e) {
501             Log.e(TAG, "Failed to notify openFailure: " + e);
502         } finally {
503             WakeLockManager.getInstance().manageWakeLockOnMediaDirectionUpdate(
504                     mSessionId, RtpConfig.MEDIA_DIRECTION_NO_FLOW);
505         }
506     }
507 
handleSessionClosed()508     private void handleSessionClosed() {
509         try {
510             mCallback.onSessionClosed();
511         }  catch (RemoteException e) {
512             Log.e(TAG, "Failed to notify SessionClosed: " + e);
513         }
514     }
515 
handleModifySessionRespose(AudioConfig config, int error)516     private void handleModifySessionRespose(AudioConfig config, int error) {
517         try {
518             if (error != ImsMediaSession.RESULT_SUCCESS) {
519                 Log.e(TAG, "modifySession failed with error: " + error);
520             }
521             mCallback.onModifySessionResponse(config, error);
522         }  catch (RemoteException e) {
523             Log.e(TAG, "Failed to notify modifySessionResponse: " + e);
524         }
525     }
526 
handleAddConfigResponse(AudioConfig config, int error)527     private void handleAddConfigResponse(AudioConfig config, int error) {
528         try {
529             mCallback.onAddConfigResponse(config, error);
530         }  catch (RemoteException e) {
531             Log.e(TAG, "Failed to notify onAddConfigResponse: " + e);
532         }
533     }
534 
handleConfirmConfigResponse(AudioConfig config, int error)535     private void handleConfirmConfigResponse(AudioConfig config, int error) {
536         try {
537             mCallback.onConfirmConfigResponse(config, error);
538         }  catch (RemoteException e) {
539             Log.e(TAG, "Failed to notify onConfirmConfigResponse: " + e);
540         }
541     }
542 
handleFirstMediaPacketInd(AudioConfig config)543     private void handleFirstMediaPacketInd(AudioConfig config) {
544         try {
545             mCallback.onFirstMediaPacketReceived(config);
546         }  catch (RemoteException e) {
547             Log.e(TAG, "Failed to notify first media packet received indication: " + e);
548         }
549     }
550 
handleRtpHeaderExtensionInd(List<RtpHeaderExtension> extensions)551     private void handleRtpHeaderExtensionInd(List<RtpHeaderExtension> extensions) {
552         try {
553             mCallback.onHeaderExtensionReceived(extensions);
554         }  catch (RemoteException e) {
555             Log.e(TAG, "Failed to notify RTP header extension: " + e);
556         }
557     }
558 
handleNotifyMediaQualityStatus(MediaQualityStatus status)559     private void handleNotifyMediaQualityStatus(MediaQualityStatus status) {
560         try {
561             mCallback.notifyMediaQualityStatus(status);
562         }  catch (RemoteException e) {
563             Log.e(TAG, "Failed to notify media quality status: " + e);
564         }
565     }
566 
handleTriggerAnbrQuery(AudioConfig config)567     private void handleTriggerAnbrQuery(AudioConfig config) {
568         try {
569             mCallback.triggerAnbrQuery(config);
570         }  catch (RemoteException e) {
571             Log.e(TAG, "Failed to trigger ANBR query: " + e);
572         }
573     }
574 
handleDtmfReceived(char dtmfDigit, int durationMs)575     private void handleDtmfReceived(char dtmfDigit, int durationMs) {
576         try {
577             mCallback.onDtmfReceived(dtmfDigit, durationMs);
578         }  catch (RemoteException e) {
579             Log.e(TAG, "Failed to Dtmf received: " + e);
580         }
581     }
582 
handleCallQualityChangeInd(CallQuality callQuality)583     private void handleCallQualityChangeInd(CallQuality callQuality) {
584         try {
585             mCallback.onCallQualityChanged(callQuality);
586         }  catch (RemoteException e) {
587             Log.e(TAG, "Failed to notify call quality changed indication: " + e);
588         }
589     }
590 
handleNotifyReceptionStats(RtpReceptionStats stats)591     private void handleNotifyReceptionStats(RtpReceptionStats stats) {
592         try {
593             mCallback.notifyRtpReceptionStats(stats);
594         }  catch (RemoteException e) {
595             Log.e(TAG, "Failed to notify rtp reception statistics: " + e);
596         }
597     }
598 }
599