blob: a5f77290e2b496a45757722ff99ef85c39a9bc05 [file] [log] [blame]
Tyler Gunnd5821842021-02-05 11:12:57 -08001/*
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
17package android.telecom;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.annotation.SdkConstant;
22import android.annotation.SystemApi;
23import android.app.Service;
24import android.content.Intent;
25import android.os.IBinder;
26import android.os.RemoteException;
Tyler Gunnbc9ecbc2021-03-09 15:06:30 -080027import android.telephony.Annotation;
28import android.telephony.ims.ImsReasonInfo;
Tyler Gunnd5821842021-02-05 11:12:57 -080029import android.util.ArrayMap;
30
31import com.android.internal.telecom.ICallDiagnosticService;
32import com.android.internal.telecom.ICallDiagnosticServiceAdapter;
33
34import java.util.Map;
35
36/**
37 * The platform supports a single OEM provided {@link CallDiagnosticService}, as defined by the
38 * {@code call_diagnostic_service_package_name} key in the
39 * {@code packages/services/Telecomm/res/values/config.xml} file. An OEM can use this API to help
40 * provide more actionable information about calling issues the user encounters during and after
41 * a call.
42 *
43 * <h1>Manifest Declaration</h1>
44 * The following is an example of how to declare the service entry in the
45 * {@link CallDiagnosticService} manifest file:
46 * <pre>
47 * {@code
48 * <service android:name="your.package.YourCallDiagnosticServiceImplementation"
49 * android:permission="android.permission.BIND_CALL_DIAGNOSTIC_SERVICE">
50 * <intent-filter>
51 * <action android:name="android.telecom.CallDiagnosticService"/>
52 * </intent-filter>
53 * </service>
54 * }
55 * </pre>
56 * @hide
57 */
58@SystemApi
59public abstract class CallDiagnosticService extends Service {
60
61 /**
62 * Binder stub implementation which handles incoming requests from Telecom.
63 */
64 private final class CallDiagnosticServiceBinder extends ICallDiagnosticService.Stub {
65
66 @Override
67 public void setAdapter(ICallDiagnosticServiceAdapter adapter) throws RemoteException {
68 handleSetAdapter(adapter);
69 }
70
71 @Override
72 public void initializeDiagnosticCall(ParcelableCall call) throws RemoteException {
73 handleCallAdded(call);
74 }
75
76 @Override
77 public void updateCall(ParcelableCall call) throws RemoteException {
78 handleCallUpdated(call);
79 }
80
81 @Override
82 public void removeDiagnosticCall(String callId) throws RemoteException {
83 handleCallRemoved(callId);
84 }
85
86 @Override
87 public void updateCallAudioState(CallAudioState callAudioState) throws RemoteException {
88 onCallAudioStateChanged(callAudioState);
89 }
90
91 @Override
92 public void receiveDeviceToDeviceMessage(String callId, int message, int value) {
93 handleReceivedD2DMessage(callId, message, value);
94 }
95
96 @Override
97 public void receiveBluetoothCallQualityReport(BluetoothCallQualityReport qualityReport)
98 throws RemoteException {
99 handleBluetoothCallQualityReport(qualityReport);
100 }
Tyler Gunnbc9ecbc2021-03-09 15:06:30 -0800101
102 @Override
103 public void notifyCallDisconnected(@NonNull String callId,
104 @NonNull DisconnectCause disconnectCause) throws RemoteException {
105 handleCallDisconnected(callId, disconnectCause);
106 }
Tyler Gunnd5821842021-02-05 11:12:57 -0800107 }
108
109 /**
110 * Listens to events raised by a {@link DiagnosticCall}.
111 */
112 private android.telecom.DiagnosticCall.Listener mDiagnosticCallListener =
113 new android.telecom.DiagnosticCall.Listener() {
114
115 @Override
116 public void onSendDeviceToDeviceMessage(DiagnosticCall diagnosticCall,
117 @DiagnosticCall.MessageType int message, int value) {
118 handleSendDeviceToDeviceMessage(diagnosticCall, message, value);
119 }
120
121 @Override
122 public void onDisplayDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId,
123 CharSequence message) {
124 handleDisplayDiagnosticMessage(diagnosticCall, messageId, message);
125 }
126
127 @Override
128 public void onClearDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId) {
129 handleClearDiagnosticMessage(diagnosticCall, messageId);
130 }
131 };
132
133 /**
134 * The {@link Intent} that must be declared as handled by the service.
135 */
136 @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
137 public static final String SERVICE_INTERFACE = "android.telecom.CallDiagnosticService";
138
139 /**
140 * Map which tracks the Telecom calls received from the Telecom stack.
141 */
142 private final Map<String, Call.Details> mCallByTelecomCallId = new ArrayMap<>();
143 private final Map<String, DiagnosticCall> mDiagnosticCallByTelecomCallId = new ArrayMap<>();
144 private ICallDiagnosticServiceAdapter mAdapter;
145
146 @Nullable
147 @Override
148 public IBinder onBind(@NonNull Intent intent) {
149 Log.i(this, "onBind!");
150 return new CallDiagnosticServiceBinder();
151 }
152
153 /**
154 * Telecom calls this method on the {@link CallDiagnosticService} with details about a new call
155 * which was added to Telecom.
156 * <p>
157 * The {@link CallDiagnosticService} returns an implementation of {@link DiagnosticCall} to be
158 * used for the lifespan of this call.
159 *
160 * @param call The details of the new call.
161 * @return An instance of {@link DiagnosticCall} which the {@link CallDiagnosticService}
162 * provides to be used for the lifespan of the call.
163 * @throws IllegalArgumentException if a {@code null} {@link DiagnosticCall} is returned.
164 */
165 public abstract @NonNull DiagnosticCall onInitializeDiagnosticCall(@NonNull
166 android.telecom.Call.Details call);
167
168 /**
169 * Telecom calls this method when a previous created {@link DiagnosticCall} is no longer needed.
170 * This happens when Telecom is no longer tracking the call in question.
171 * @param call The diagnostic call which is no longer tracked by Telecom.
172 */
173 public abstract void onRemoveDiagnosticCall(@NonNull DiagnosticCall call);
174
175 /**
176 * Telecom calls this method when the audio routing or available audio route information
177 * changes.
178 * <p>
179 * Audio state is common to all calls.
180 *
181 * @param audioState The new audio state.
182 */
183 public abstract void onCallAudioStateChanged(
184 @NonNull CallAudioState audioState);
185
186 /**
187 * Telecom calls this method when a {@link BluetoothCallQualityReport} is received from the
188 * bluetooth stack.
189 * @param qualityReport the {@link BluetoothCallQualityReport}.
190 */
191 public abstract void onBluetoothCallQualityReportReceived(
192 @NonNull BluetoothCallQualityReport qualityReport);
193
194 /**
195 * Handles a request from Telecom to set the adapater used to communicate back to Telecom.
196 * @param adapter
197 */
198 private void handleSetAdapter(@NonNull ICallDiagnosticServiceAdapter adapter) {
199 mAdapter = adapter;
200 }
201
202 /**
203 * Handles a request from Telecom to add a new call.
204 * @param parcelableCall
205 */
206 private void handleCallAdded(@NonNull ParcelableCall parcelableCall) {
207 String telecomCallId = parcelableCall.getId();
208 Log.i(this, "handleCallAdded: callId=%s - added", telecomCallId);
209 Call.Details newCallDetails = Call.Details.createFromParcelableCall(parcelableCall);
210 mCallByTelecomCallId.put(telecomCallId, newCallDetails);
211
212 DiagnosticCall diagnosticCall = onInitializeDiagnosticCall(newCallDetails);
213 if (diagnosticCall == null) {
214 throw new IllegalArgumentException("A valid DiagnosticCall instance was not provided.");
215 }
216 diagnosticCall.setListener(mDiagnosticCallListener);
217 diagnosticCall.setCallId(telecomCallId);
218 mDiagnosticCallByTelecomCallId.put(telecomCallId, diagnosticCall);
219 }
220
221 /**
222 * Handles an update to {@link Call.Details} notified by Telecom.
223 * Caches the call details and notifies the {@link DiagnosticCall} of the change via
224 * {@link DiagnosticCall#onCallDetailsChanged(Call.Details)}.
225 * @param parcelableCall the new parceled call details from Telecom.
226 */
227 private void handleCallUpdated(@NonNull ParcelableCall parcelableCall) {
228 String telecomCallId = parcelableCall.getId();
229 Log.i(this, "handleCallUpdated: callId=%s - updated", telecomCallId);
230 Call.Details newCallDetails = Call.Details.createFromParcelableCall(parcelableCall);
231
232 DiagnosticCall diagnosticCall = mDiagnosticCallByTelecomCallId.get(telecomCallId);
233 mCallByTelecomCallId.put(telecomCallId, newCallDetails);
234 diagnosticCall.handleCallUpdated(newCallDetails);
235 }
236
237 /**
238 * Handles a request from Telecom to remove an existing call.
239 * @param telecomCallId
240 */
241 private void handleCallRemoved(@NonNull String telecomCallId) {
242 Log.i(this, "handleCallRemoved: callId=%s - removed", telecomCallId);
243
244 if (mCallByTelecomCallId.containsKey(telecomCallId)) {
245 mCallByTelecomCallId.remove(telecomCallId);
246 }
247 if (mDiagnosticCallByTelecomCallId.containsKey(telecomCallId)) {
248 DiagnosticCall call = mDiagnosticCallByTelecomCallId.remove(telecomCallId);
249 // Inform the service of the removed call.
250 onRemoveDiagnosticCall(call);
251 }
252 }
253
254 /**
255 * Handles an incoming device to device message received from Telecom. Notifies the
256 * {@link DiagnosticCall} via {@link DiagnosticCall#onReceiveDeviceToDeviceMessage(int, int)}.
257 * @param callId
258 * @param message
259 * @param value
260 */
261 private void handleReceivedD2DMessage(@NonNull String callId, int message, int value) {
262 Log.i(this, "handleReceivedD2DMessage: callId=%s, msg=%d/%d", callId, message, value);
263 DiagnosticCall diagnosticCall = mDiagnosticCallByTelecomCallId.get(callId);
264 diagnosticCall.onReceiveDeviceToDeviceMessage(message, value);
265 }
266
267 /**
Tyler Gunnbc9ecbc2021-03-09 15:06:30 -0800268 * Handles a request from the Telecom framework to get a disconnect message from the
269 * {@link CallDiagnosticService}.
270 * @param callId The ID of the call.
271 * @param disconnectCause The telecom disconnect cause.
272 */
273 private void handleCallDisconnected(@NonNull String callId,
274 @NonNull DisconnectCause disconnectCause) {
275 Log.i(this, "handleCallDisconnected: call=%s; cause=%s", callId, disconnectCause);
276 DiagnosticCall diagnosticCall = mDiagnosticCallByTelecomCallId.get(callId);
277 CharSequence message;
278 if (disconnectCause.getImsReasonInfo() != null) {
279 message = diagnosticCall.onCallDisconnected(disconnectCause.getImsReasonInfo());
280 } else {
281 message = diagnosticCall.onCallDisconnected(
282 disconnectCause.getTelephonyDisconnectCause(),
283 disconnectCause.getTelephonyPreciseDisconnectCause());
284 }
285 try {
286 mAdapter.overrideDisconnectMessage(callId, message);
287 } catch (RemoteException e) {
288 Log.w(this, "handleCallDisconnected: call=%s; cause=%s; %s",
289 callId, disconnectCause, e);
290 }
291 }
292
293 /**
Tyler Gunnd5821842021-02-05 11:12:57 -0800294 * Handles an incoming bluetooth call quality report from Telecom. Notifies via
295 * {@link CallDiagnosticService#onBluetoothCallQualityReportReceived(
296 * BluetoothCallQualityReport)}.
297 * @param qualityReport The bluetooth call quality remote.
298 */
299 private void handleBluetoothCallQualityReport(@NonNull BluetoothCallQualityReport
300 qualityReport) {
301 Log.i(this, "handleBluetoothCallQualityReport; report=%s", qualityReport);
302 onBluetoothCallQualityReportReceived(qualityReport);
303 }
304
305 /**
306 * Handles a request from a {@link DiagnosticCall} to send a device to device message (received
307 * via {@link DiagnosticCall#sendDeviceToDeviceMessage(int, int)}.
308 * @param diagnosticCall
309 * @param message
310 * @param value
311 */
312 private void handleSendDeviceToDeviceMessage(@NonNull DiagnosticCall diagnosticCall,
313 int message, int value) {
314 String callId = diagnosticCall.getCallId();
315 try {
316 mAdapter.sendDeviceToDeviceMessage(callId, message, value);
317 Log.i(this, "handleSendDeviceToDeviceMessage: call=%s; msg=%d/%d", callId, message,
318 value);
319 } catch (RemoteException e) {
320 Log.w(this, "handleSendDeviceToDeviceMessage: call=%s; msg=%d/%d failed %s",
321 callId, message, value, e);
322 }
323 }
324
325 /**
326 * Handles a request from a {@link DiagnosticCall} to display an in-call diagnostic message.
327 * Originates from {@link DiagnosticCall#displayDiagnosticMessage(int, CharSequence)}.
328 * @param diagnosticCall
329 * @param messageId
330 * @param message
331 */
332 private void handleDisplayDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId,
333 CharSequence message) {
334 String callId = diagnosticCall.getCallId();
335 try {
336 mAdapter.displayDiagnosticMessage(callId, messageId, message);
337 Log.i(this, "handleDisplayDiagnosticMessage: call=%s; msg=%d/%s", callId, messageId,
338 message);
339 } catch (RemoteException e) {
340 Log.w(this, "handleDisplayDiagnosticMessage: call=%s; msg=%d/%s failed %s",
341 callId, messageId, message, e);
342 }
343 }
344
345 /**
346 * Handles a request from a {@link DiagnosticCall} to clear a previously shown diagnostic
347 * message.
348 * Originates from {@link DiagnosticCall#clearDiagnosticMessage(int)}.
349 * @param diagnosticCall
350 * @param messageId
351 */
352 private void handleClearDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId) {
353 String callId = diagnosticCall.getCallId();
354 try {
355 mAdapter.clearDiagnosticMessage(callId, messageId);
356 Log.i(this, "handleClearDiagnosticMessage: call=%s; msg=%d", callId, messageId);
357 } catch (RemoteException e) {
358 Log.w(this, "handleClearDiagnosticMessage: call=%s; msg=%d failed %s",
359 callId, messageId, e);
360 }
361 }
362}