• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.server.telecom;
18 
19 import android.Manifest;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.RequiresPermission;
23 import android.annotation.UserIdInt;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.ServiceConnection;
28 import android.content.pm.PackageManager;
29 import android.content.pm.ResolveInfo;
30 import android.os.Bundle;
31 import android.os.IBinder;
32 import android.os.RemoteException;
33 import android.os.UserHandle;
34 import android.telecom.BluetoothCallQualityReport;
35 import android.telecom.CallAudioState;
36 import android.telecom.CallDiagnosticService;
37 import android.telecom.ConnectionService;
38 import android.telecom.CallDiagnostics;
39 import android.telecom.DisconnectCause;
40 import android.telecom.InCallService;
41 import android.telecom.Log;
42 import android.telecom.ParcelableCall;
43 import android.telephony.CallQuality;
44 import android.telephony.ims.ImsReasonInfo;
45 import android.text.TextUtils;
46 
47 import com.android.internal.telecom.ICallDiagnosticService;
48 import com.android.internal.util.IndentingPrintWriter;
49 
50 import java.util.List;
51 
52 /**
53  * Responsible for maintaining binding to the {@link CallDiagnosticService} defined by the
54  * {@code call_diagnostic_service_package_name} key in the
55  * {@code packages/services/Telecomm/res/values/config.xml} file.
56  */
57 public class CallDiagnosticServiceController extends CallsManagerListenerBase {
58     /**
59      * Context dependencies for the {@link CallDiagnosticServiceController}.
60      */
61     public interface ContextProxy {
queryIntentServicesAsUser(@onNull Intent intent, @PackageManager.ResolveInfoFlags int flags, @UserIdInt int userId)62         List<ResolveInfo> queryIntentServicesAsUser(@NonNull Intent intent,
63                 @PackageManager.ResolveInfoFlags int flags, @UserIdInt int userId);
bindServiceAsUser(@onNull @equiresPermission Intent service, @NonNull ServiceConnection conn, int flags, @NonNull UserHandle user)64         boolean bindServiceAsUser(@NonNull @RequiresPermission Intent service,
65                 @NonNull ServiceConnection conn, int flags, @NonNull UserHandle user);
unbindService(@onNull ServiceConnection conn)66         void unbindService(@NonNull ServiceConnection conn);
getCurrentUserHandle()67         UserHandle getCurrentUserHandle();
68     }
69 
70     /**
71      * Listener for {@link Call} events; used to propagate these changes to the
72      * {@link CallDiagnosticService}.
73      */
74     private final Call.Listener mCallListener = new Call.ListenerBase() {
75         @Override
76         public void onConnectionCapabilitiesChanged(Call call) {
77             updateCall(call);
78         }
79 
80         @Override
81         public void onConnectionPropertiesChanged(Call call, boolean didRttChange) {
82             updateCall(call);
83         }
84 
85         /**
86          * Listens for changes to extras reported by a Telecom {@link Call}.
87          *
88          * Extras changes can originate from a {@link ConnectionService} or an {@link InCallService}
89          * so we will only trigger an update of the call information if the source of the extras
90          * change was a {@link ConnectionService}.
91          *
92          * @param call The call.
93          * @param source The source of the extras change ({@link Call#SOURCE_CONNECTION_SERVICE} or
94          *               {@link Call#SOURCE_INCALL_SERVICE}).
95          * @param extras The extras.
96          */
97         @Override
98         public void onExtrasChanged(Call call, int source, Bundle extras) {
99             // Do not inform InCallServices of changes which originated there.
100             if (source == Call.SOURCE_INCALL_SERVICE) {
101                 return;
102             }
103             updateCall(call);
104         }
105 
106         /**
107          * Listens for changes to extras reported by a Telecom {@link Call}.
108          *
109          * Extras changes can originate from a {@link ConnectionService} or an {@link InCallService}
110          * so we will only trigger an update of the call information if the source of the extras
111          * change was a {@link ConnectionService}.
112          *  @param call The call.
113          * @param source The source of the extras change ({@link Call#SOURCE_CONNECTION_SERVICE} or
114          *               {@link Call#SOURCE_INCALL_SERVICE}).
115          * @param keys The extra key removed
116          */
117         @Override
118         public void onExtrasRemoved(Call call, int source, List<String> keys) {
119             // Do not inform InCallServices of changes which originated there.
120             if (source == Call.SOURCE_INCALL_SERVICE) {
121                 return;
122             }
123             updateCall(call);
124         }
125 
126         /**
127          * Handles changes to the video state of a call.
128          * @param call
129          * @param previousVideoState
130          * @param newVideoState
131          */
132         @Override
133         public void onVideoStateChanged(Call call, int previousVideoState, int newVideoState) {
134             updateCall(call);
135         }
136 
137         /**
138          * Relays a bluetooth call quality report received from the Bluetooth stack to the
139          * CallDiagnosticService.
140          * @param call The call.
141          * @param report The received report.
142          */
143         @Override
144         public void onBluetoothCallQualityReport(Call call, BluetoothCallQualityReport report) {
145             handleBluetoothCallQualityReport(call, report);
146         }
147 
148         /**
149          * Relays a device to device message received from Telephony to the CallDiagnosticService.
150          * @param call
151          * @param messageType
152          * @param messageValue
153          */
154         @Override
155         public void onReceivedDeviceToDeviceMessage(Call call, int messageType, int messageValue) {
156             handleReceivedDeviceToDeviceMessage(call, messageType, messageValue);
157         }
158 
159         /**
160          * Handles an incoming {@link CallQuality} report from a {@link android.telecom.Connection}.
161          * @param call The call.
162          * @param callQualityReport The call quality report.
163          */
164         @Override
165         public void onReceivedCallQualityReport(Call call, CallQuality callQualityReport) {
166             handleCallQualityReport(call, callQualityReport);
167         }
168     };
169 
170     /**
171      * {@link ServiceConnection} handling changes to binding of the {@link CallDiagnosticService}.
172      */
173     private class CallDiagnosticServiceConnection implements ServiceConnection {
174         @Override
onServiceConnected(ComponentName name, IBinder service)175         public void onServiceConnected(ComponentName name, IBinder service) {
176             Log.startSession("CDSC.oSC", Log.getPackageAbbreviation(name));
177             try {
178                 synchronized (mLock) {
179                     mCallDiagnosticService = ICallDiagnosticService.Stub.asInterface(service);
180 
181                     handleConnectionComplete(mCallDiagnosticService);
182                 }
183                 Log.i(CallDiagnosticServiceController.this, "onServiceConnected: cmp=%s", name);
184             } finally {
185                 Log.endSession();
186             }
187         }
188 
189         @Override
onServiceDisconnected(ComponentName name)190         public void onServiceDisconnected(ComponentName name) {
191             Log.startSession("CDSC.oSD", Log.getPackageAbbreviation(name));
192             try {
193                 synchronized (mLock) {
194                     mCallDiagnosticService = null;
195                     mConnection = null;
196                 }
197                 Log.i(CallDiagnosticServiceController.this, "onServiceDisconnected: cmp=%s", name);
198             } finally {
199                 Log.endSession();
200             }
201         }
202 
203         @Override
onBindingDied(ComponentName name)204         public void onBindingDied(ComponentName name) {
205             Log.startSession("CDSC.oBD", Log.getPackageAbbreviation(name));
206             try {
207                 synchronized (mLock) {
208                     mCallDiagnosticService = null;
209                     mConnection = null;
210                 }
211                 Log.w(CallDiagnosticServiceController.this, "onBindingDied: cmp=%s", name);
212             } finally {
213                 Log.endSession();
214             }
215         }
216 
217         @Override
onNullBinding(ComponentName name)218         public void onNullBinding(ComponentName name) {
219             Log.startSession("CDSC.oNB", Log.getPackageAbbreviation(name));
220             try {
221                 synchronized (mLock) {
222                     maybeUnbindCallScreeningService();
223                 }
224             } finally {
225                 Log.endSession();
226             }
227         }
228     }
229 
230     private final String mPackageName;
231     private final ContextProxy mContextProxy;
232     private InCallTonePlayer.Factory mPlayerFactory;
233     private String mTestPackageName;
234     private CallDiagnosticServiceConnection mConnection;
235     private CallDiagnosticServiceAdapter mAdapter;
236     private final TelecomSystem.SyncRoot mLock;
237     private ICallDiagnosticService mCallDiagnosticService;
238     private final CallIdMapper mCallIdMapper = new CallIdMapper(Call::getId);
239 
CallDiagnosticServiceController(@onNull ContextProxy contextProxy, @Nullable String packageName, @NonNull TelecomSystem.SyncRoot lock)240     public CallDiagnosticServiceController(@NonNull ContextProxy contextProxy,
241             @Nullable String packageName, @NonNull TelecomSystem.SyncRoot lock) {
242         mContextProxy = contextProxy;
243         mPackageName = packageName;
244         mLock = lock;
245     }
246 
247     /**
248      * Sets the current {@link InCallTonePlayer.Factory} for this instance.
249      * @param factory the factory.
250      */
setInCallTonePlayerFactory(InCallTonePlayer.Factory factory)251     public void setInCallTonePlayerFactory(InCallTonePlayer.Factory factory) {
252         mPlayerFactory = factory;
253     }
254 
255     /**
256      * Handles Telecom adding new calls.  Will bind to the call diagnostic service if needed and
257      * send the calls, or send to an already bound service.
258      * @param call The call to add.
259      */
260     @Override
onCallAdded(@onNull Call call)261     public void onCallAdded(@NonNull Call call) {
262         if (!call.isSimCall() || call.isExternalCall()) {
263             Log.i(this, "onCallAdded: skipping call %s as non-sim or external.", call.getId());
264             return;
265         }
266         if (mCallIdMapper.getCallId(call) == null) {
267             mCallIdMapper.addCall(call);
268             call.addListener(mCallListener);
269         }
270         if (isConnected()) {
271             sendCallToBoundService(call, mCallDiagnosticService);
272         } else {
273             maybeBindCallDiagnosticService();
274         }
275     }
276 
277     /**
278      * Handles a newly disconnected call signalled from {@link CallsManager}.
279      * @param call The call
280      * @param disconnectCause The disconnect cause
281      * @return {@code true} if the {@link CallDiagnosticService} was sent the call, {@code false}
282      * if the call was not applicable to the CDS or if there was an issue sending it.
283      */
onCallDisconnected(@onNull Call call, @NonNull DisconnectCause disconnectCause)284     public boolean onCallDisconnected(@NonNull Call call,
285             @NonNull DisconnectCause disconnectCause) {
286         if (!call.isSimCall() || call.isExternalCall()) {
287             Log.i(this, "onCallDisconnected: skipping call %s as non-sim or external.",
288                     call.getId());
289             return false;
290         }
291         String callId = mCallIdMapper.getCallId(call);
292         try {
293             if (isConnected()) {
294                 mCallDiagnosticService.notifyCallDisconnected(callId, disconnectCause);
295                 return true;
296             }
297         } catch (RemoteException e) {
298             Log.w(this, "onCallDisconnected: callId=%s, exception=%s", call.getId(), e);
299         }
300         return false;
301     }
302 
303     /**
304      * Handles Telecom removal of calls; will remove the call from the bound service and if the
305      * number of tracked calls falls to zero, unbind from the service.
306      * @param call The call to remove from the bound CDS.
307      */
308     @Override
onCallRemoved(@onNull Call call)309     public void onCallRemoved(@NonNull Call call) {
310         if (!call.isSimCall() || call.isExternalCall()) {
311             Log.i(this, "onCallRemoved: skipping call %s as non-sim or external.", call.getId());
312             return;
313         }
314         mCallIdMapper.removeCall(call);
315         call.removeListener(mCallListener);
316         removeCallFromBoundService(call, mCallDiagnosticService);
317 
318         if (mCallIdMapper.getCalls().size() == 0) {
319             maybeUnbindCallScreeningService();
320         }
321     }
322 
323     @Override
onCallStateChanged(Call call, int oldState, int newState)324     public void onCallStateChanged(Call call, int oldState, int newState) {
325         updateCall(call);
326     }
327 
328     @Override
onCallAudioStateChanged(CallAudioState oldCallAudioState, CallAudioState newCallAudioState)329     public void onCallAudioStateChanged(CallAudioState oldCallAudioState,
330             CallAudioState newCallAudioState) {
331         if (mCallDiagnosticService != null) {
332             try {
333                 mCallDiagnosticService.updateCallAudioState(newCallAudioState);
334             } catch (RemoteException e) {
335                 Log.w(this, "onCallAudioStateChanged: failed %s", e);
336             }
337         }
338     }
339 
340     /**
341      * Sets the test call diagnostic service; used by the telecom command line command to override
342      * the {@link CallDiagnosticService} to bind to for CTS test purposes.
343      * @param packageName The package name to set to.
344      */
setTestCallDiagnosticService(@ullable String packageName)345     public void setTestCallDiagnosticService(@Nullable String packageName) {
346         if (TextUtils.isEmpty(packageName)) {
347             mTestPackageName = null;
348         } else {
349             mTestPackageName = packageName;
350         }
351 
352         Log.i(this, "setTestCallDiagnosticService: packageName=%s", packageName);
353     }
354 
355     /**
356      * Determines the active call diagnostic service, taking into account the test override.
357      * @return The package name of the active call diagnostic service.
358      */
getActiveCallDiagnosticService()359     private @Nullable String getActiveCallDiagnosticService() {
360         if (mTestPackageName != null) {
361             return mTestPackageName;
362         }
363 
364         return mPackageName;
365     }
366 
367     /**
368      * If we are not already bound to the {@link CallDiagnosticService}, attempts to initiate a
369      * binding tho that service.
370      * @return {@code true} if we bound, {@code false} otherwise.
371      */
maybeBindCallDiagnosticService()372     private boolean maybeBindCallDiagnosticService() {
373         if (mConnection != null) {
374             return false;
375         }
376 
377         mConnection = new CallDiagnosticServiceConnection();
378         boolean bound = bindCallDiagnosticService(mContextProxy.getCurrentUserHandle(),
379                 getActiveCallDiagnosticService(), mConnection);
380         if (!bound) {
381             mConnection = null;
382         }
383         return bound;
384     }
385 
386     /**
387      * Performs binding to the {@link CallDiagnosticService}.
388      * @param userHandle user name to bind via.
389      * @param packageName package name of the CDS.
390      * @param serviceConnection The service connection to be notified of bind events.
391      * @return
392      */
bindCallDiagnosticService(UserHandle userHandle, String packageName, CallDiagnosticServiceConnection serviceConnection)393     private boolean bindCallDiagnosticService(UserHandle userHandle,
394             String packageName, CallDiagnosticServiceConnection serviceConnection) {
395 
396         if (TextUtils.isEmpty(packageName)) {
397             Log.i(this, "bindCallDiagnosticService: no package; skip binding.");
398             return false;
399         }
400 
401         Intent intent = new Intent(CallDiagnosticService.SERVICE_INTERFACE)
402                 .setPackage(packageName);
403         Log.i(this, "bindCallDiagnosticService: user %d.", userHandle.getIdentifier());
404         List<ResolveInfo> entries = mContextProxy.queryIntentServicesAsUser(intent, 0,
405                 userHandle.getIdentifier());
406         if (entries.isEmpty()) {
407             Log.i(this, "bindCallDiagnosticService: %s has no service.", packageName);
408             return false;
409         }
410 
411         ResolveInfo entry = entries.get(0);
412         if (entry.serviceInfo == null) {
413             Log.i(this, "bindCallDiagnosticService: %s has no service info.", packageName);
414             return false;
415         }
416 
417         if (entry.serviceInfo.permission == null || !entry.serviceInfo.permission.equals(
418                 Manifest.permission.BIND_CALL_DIAGNOSTIC_SERVICE)) {
419             Log.i(this, "bindCallDiagnosticService: %s doesn't require "
420                     + "BIND_CALL_DIAGNOSTIC_SERVICE.", packageName);
421             return false;
422         }
423 
424         ComponentName componentName =
425                 new ComponentName(entry.serviceInfo.packageName, entry.serviceInfo.name);
426         intent.setComponent(componentName);
427         if (mContextProxy.bindServiceAsUser(
428                 intent,
429                 serviceConnection,
430                 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
431                 UserHandle.CURRENT)) {
432             Log.d(this, "bindCallDiagnosticService, found service, waiting for it to connect");
433             return true;
434         }
435         return false;
436     }
437 
438     /**
439      * If we are bound to a {@link CallDiagnosticService}, unbind from it.
440      */
maybeUnbindCallScreeningService()441     public void maybeUnbindCallScreeningService() {
442         if (mConnection != null) {
443             Log.i(this, "maybeUnbindCallScreeningService - unbinding from %s",
444                     getActiveCallDiagnosticService());
445             try {
446                 mContextProxy.unbindService(mConnection);
447                 mCallDiagnosticService = null;
448                 mConnection = null;
449             } catch (IllegalArgumentException e) {
450                 Log.i(this, "maybeUnbindCallScreeningService: Exception when unbind %s : %s",
451                         getActiveCallDiagnosticService(), e.getMessage());
452             }
453         } else {
454             Log.w(this, "maybeUnbindCallScreeningService - already unbound");
455         }
456     }
457 
458     /**
459      * Implements the abstracted Telecom functionality the {@link CallDiagnosticServiceAdapter}
460      * depends on.
461      */
462     private CallDiagnosticServiceAdapter.TelecomAdapter mTelecomAdapter =
463             new CallDiagnosticServiceAdapter.TelecomAdapter() {
464 
465         @Override
466         public void displayDiagnosticMessage(String callId, int messageId, CharSequence message) {
467             handleDisplayDiagnosticMessage(callId, messageId, message);
468         }
469 
470         @Override
471         public void clearDiagnosticMessage(String callId, int messageId) {
472             handleClearDiagnosticMessage(callId, messageId);
473         }
474 
475         @Override
476         public void sendDeviceToDeviceMessage(String callId,
477                         @CallDiagnostics.MessageType int message, int value) {
478             handleSendD2DMessage(callId, message, value);
479         }
480 
481         @Override
482         public void overrideDisconnectMessage(String callId, CharSequence message) {
483             handleOverrideDisconnectMessage(callId, message);
484         }
485     };
486 
487     /**
488      * Sends all calls to the specified {@link CallDiagnosticService}.
489      * @param callDiagnosticService the CDS to send calls to.
490      */
handleConnectionComplete(@onNull ICallDiagnosticService callDiagnosticService)491     private void handleConnectionComplete(@NonNull ICallDiagnosticService callDiagnosticService) {
492         mAdapter = new CallDiagnosticServiceAdapter(mTelecomAdapter,
493                 getActiveCallDiagnosticService(), mLock);
494         try {
495             // Add adapter for communication back from the call diagnostic service to Telecom.
496             callDiagnosticService.setAdapter(mAdapter);
497 
498             // Loop through all the calls we've got ready to send since binding.
499             for (Call call : mCallIdMapper.getCalls()) {
500                 sendCallToBoundService(call, callDiagnosticService);
501             }
502         } catch (RemoteException e) {
503             Log.w(this, "handleConnectionComplete: error=%s", e);
504         }
505     }
506 
507     /**
508      * Handles a request from a {@link CallDiagnosticService} to display a diagnostic message.
509      * @param callId the ID of the call to display the message for.
510      * @param message the message.
511      */
handleDisplayDiagnosticMessage(@onNull String callId, int messageId, @Nullable CharSequence message)512     private void handleDisplayDiagnosticMessage(@NonNull String callId, int messageId,
513             @Nullable CharSequence message) {
514         Call call = mCallIdMapper.getCall(callId);
515         if (call == null) {
516             Log.w(this, "handleDisplayDiagnosticMessage: callId=%s; msg=%d/%s; invalid call",
517                     callId, messageId, message);
518             return;
519         }
520         Log.i(this, "handleDisplayDiagnosticMessage: callId=%s; msg=%d/%s",
521                 callId, messageId, message);
522         if (mPlayerFactory != null) {
523             // Play that tone!
524             mPlayerFactory.createPlayer(InCallTonePlayer.TONE_IN_CALL_QUALITY_NOTIFICATION)
525                     .startTone();
526         }
527         call.displayDiagnosticMessage(messageId, message);
528     }
529 
530     /**
531      * Handles a request from a {@link CallDiagnosticService} to clear a previously displayed
532      * diagnostic message.
533      * @param callId the ID of the call to display the message for.
534      * @param messageId the message ID which was previous posted.
535      */
handleClearDiagnosticMessage(@onNull String callId, int messageId)536     private void handleClearDiagnosticMessage(@NonNull String callId, int messageId) {
537         Call call = mCallIdMapper.getCall(callId);
538         if (call == null) {
539             Log.w(this, "handleClearDiagnosticMessage: callId=%s; msg=%d; invalid call",
540                     callId, messageId);
541             return;
542         }
543         Log.i(this, "handleClearDiagnosticMessage: callId=%s; msg=%d; invalid call",
544                 callId, messageId);
545         call.clearDiagnosticMessage(messageId);
546     }
547 
548     /**
549      * Handles a request from a {@link CallDiagnosticService} to send a device to device message.
550      * @param callId The ID of the call to send the D2D message for.
551      * @param message The message type.
552      * @param value The message value.
553      */
handleSendD2DMessage(@onNull String callId, @CallDiagnostics.MessageType int message, int value)554     private void handleSendD2DMessage(@NonNull String callId,
555             @CallDiagnostics.MessageType int message, int value) {
556         Call call = mCallIdMapper.getCall(callId);
557         if (call == null) {
558             Log.w(this, "handleSendD2DMessage: callId=%s; msg=%d/%d; invalid call", callId,
559                     message, value);
560             return;
561         }
562         Log.i(this, "handleSendD2DMessage: callId=%s; msg=%d/%d", callId, message, value);
563         call.sendDeviceToDeviceMessage(message, value);
564     }
565 
566     /**
567      * Handles a request from a {@link CallDiagnosticService} to override the disconnect message
568      * for a call.  This is the response path from a previous call into the
569      * {@link CallDiagnosticService} via {@link CallDiagnostics#onCallDisconnected(ImsReasonInfo)}.
570      * @param callId The telecom call ID the disconnect override is pending for.
571      * @param message The new disconnect message, or {@code null} if no override.
572      */
handleOverrideDisconnectMessage(@onNull String callId, @Nullable CharSequence message)573     private void handleOverrideDisconnectMessage(@NonNull String callId,
574             @Nullable CharSequence message) {
575         Call call = mCallIdMapper.getCall(callId);
576         if (call == null) {
577             Log.w(this, "handleOverrideDisconnectMessage: callId=%s; msg=%s; invalid call", callId,
578                     message);
579             return;
580         }
581         Log.i(this, "handleOverrideDisconnectMessage: callId=%s; msg=%s", callId, message);
582         call.handleOverrideDisconnectMessage(message);
583     }
584 
585     /**
586      * Sends a single call to the bound {@link CallDiagnosticService}.
587      * @param call The call to send.
588      * @param callDiagnosticService The CDS to send it to.
589      */
sendCallToBoundService(@onNull Call call, @NonNull ICallDiagnosticService callDiagnosticService)590     private void sendCallToBoundService(@NonNull Call call,
591             @NonNull ICallDiagnosticService callDiagnosticService) {
592         try {
593             if (isConnected()) {
594                 Log.w(this, "sendCallToBoundService: initializing %s", call.getId());
595                 callDiagnosticService.initializeDiagnosticCall(getParceledCall(call));
596             } else {
597                 Log.w(this, "sendCallToBoundService: not bound, skipping %s", call.getId());
598             }
599         } catch (RemoteException e) {
600             Log.w(this, "sendCallToBoundService: callId=%s, exception=%s", call.getId(), e);
601         }
602     }
603 
604     /**
605      * Removes a call from a bound {@link CallDiagnosticService}.
606      * @param call The call to remove.
607      * @param callDiagnosticService The CDS to remove it from.
608      */
removeCallFromBoundService(@onNull Call call, @NonNull ICallDiagnosticService callDiagnosticService)609     private void removeCallFromBoundService(@NonNull Call call,
610             @NonNull ICallDiagnosticService callDiagnosticService) {
611         try {
612             if (isConnected()) {
613                 callDiagnosticService.removeDiagnosticCall(call.getId());
614             }
615         } catch (RemoteException e) {
616             Log.w(this, "removeCallFromBoundService: callId=%s, exception=%s", call.getId(), e);
617         }
618     }
619 
620     /**
621      * @return {@code true} if the call diagnostic service is bound/connected.
622      */
isConnected()623     public boolean isConnected() {
624         return mCallDiagnosticService != null;
625     }
626 
627     /**
628      * Updates the Call diagnostic service with changes to a call.
629      * @param call The updated call.
630      */
updateCall(@onNull Call call)631     private void updateCall(@NonNull Call call) {
632         try {
633             if (isConnected()) {
634                 mCallDiagnosticService.updateCall(getParceledCall(call));
635             }
636         } catch (RemoteException e) {
637             Log.w(this, "updateCall: callId=%s, exception=%s", call.getId(), e);
638         }
639     }
640 
641     /**
642      * Updates the call diagnostic service with a received bluetooth quality report.
643      * @param call The call.
644      * @param report The bluetooth call quality report.
645      */
handleBluetoothCallQualityReport(@onNull Call call, @NonNull BluetoothCallQualityReport report)646     private void handleBluetoothCallQualityReport(@NonNull Call call,
647             @NonNull BluetoothCallQualityReport report) {
648         try {
649             if (isConnected()) {
650                 mCallDiagnosticService.receiveBluetoothCallQualityReport(report);
651             }
652         } catch (RemoteException e) {
653             Log.w(this, "handleBluetoothCallQualityReport: callId=%s, exception=%s", call.getId(),
654                     e);
655         }
656     }
657 
658     /**
659      * Informs a CallDiagnosticService of an incoming device to device message which was received
660      * via the carrier network.
661      * @param call the call the message was received via.
662      * @param messageType The message type.
663      * @param messageValue The message value.
664      */
handleReceivedDeviceToDeviceMessage(@onNull Call call, int messageType, int messageValue)665     private void handleReceivedDeviceToDeviceMessage(@NonNull Call call, int messageType,
666             int messageValue) {
667         try {
668             if (isConnected()) {
669                 mCallDiagnosticService.receiveDeviceToDeviceMessage(call.getId(), messageType,
670                         messageValue);
671             }
672         } catch (RemoteException e) {
673             Log.w(this, "handleReceivedDeviceToDeviceMessage: callId=%s, exception=%s",
674                     call.getId(), e);
675         }
676     }
677 
678     /**
679      * Handles a reported {@link CallQuality} report from a {@link android.telecom.Connection}.
680      * @param call The call the report originated from.
681      * @param callQualityReport The {@link CallQuality} report.
682      */
handleCallQualityReport(@onNull Call call, @NonNull CallQuality callQualityReport)683     private void handleCallQualityReport(@NonNull Call call,
684             @NonNull CallQuality callQualityReport) {
685         try {
686             if (isConnected()) {
687                 mCallDiagnosticService.callQualityChanged(call.getId(), callQualityReport);
688             }
689         } catch (RemoteException e) {
690             Log.w(this, "handleCallQualityReport: callId=%s, exception=%s",
691                     call.getId(), e);
692         }
693     }
694 
695     /**
696      * Get a parcelled representation of a call for transport to the service.
697      * @param call The call.
698      * @return The parcelled call.
699      */
getParceledCall(@onNull Call call)700     private @NonNull ParcelableCall getParceledCall(@NonNull Call call) {
701         return ParcelableCallUtils.toParcelableCall(
702                 call,
703                 false /* includeVideoProvider */,
704                 null /* phoneAcctRegistrar */,
705                 false /* supportsExternalCalls */,
706                 false /* includeRttCall */,
707                 false /* isForSystemDialer */
708         );
709     }
710 
711     /**
712      * Dumps the state of the {@link CallDiagnosticServiceController}.
713      *
714      * @param pw The {@code IndentingPrintWriter} to write the state to.
715      */
dump(IndentingPrintWriter pw)716     public void dump(IndentingPrintWriter pw) {
717         pw.print("activeCallDiagnosticService: ");
718         pw.println(getActiveCallDiagnosticService());
719         pw.print("isConnected: ");
720         pw.println(isConnected());
721     }
722 }
723