blob: 5fb6b3381360d9895b37c13d674cad190ea03474 [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;
Tyler Gunn80196212021-03-11 22:43:09 -080022import android.annotation.SuppressLint;
Tyler Gunnd5821842021-02-05 11:12:57 -080023import android.annotation.SystemApi;
24import android.app.Service;
25import android.content.Intent;
Tyler Gunn80196212021-03-11 22:43:09 -080026import android.os.Handler;
27import android.os.HandlerExecutor;
Tyler Gunnd5821842021-02-05 11:12:57 -080028import android.os.IBinder;
29import android.os.RemoteException;
Tyler Gunnbc9ecbc2021-03-09 15:06:30 -080030import android.telephony.Annotation;
31import android.telephony.ims.ImsReasonInfo;
Tyler Gunnd5821842021-02-05 11:12:57 -080032import android.util.ArrayMap;
33
34import com.android.internal.telecom.ICallDiagnosticService;
35import com.android.internal.telecom.ICallDiagnosticServiceAdapter;
36
37import java.util.Map;
Tyler Gunn80196212021-03-11 22:43:09 -080038import java.util.concurrent.Executor;
Tyler Gunnd5821842021-02-05 11:12:57 -080039
40/**
41 * The platform supports a single OEM provided {@link CallDiagnosticService}, as defined by the
42 * {@code call_diagnostic_service_package_name} key in the
43 * {@code packages/services/Telecomm/res/values/config.xml} file. An OEM can use this API to help
44 * provide more actionable information about calling issues the user encounters during and after
45 * a call.
46 *
47 * <h1>Manifest Declaration</h1>
48 * The following is an example of how to declare the service entry in the
49 * {@link CallDiagnosticService} manifest file:
50 * <pre>
51 * {@code
52 * <service android:name="your.package.YourCallDiagnosticServiceImplementation"
53 * android:permission="android.permission.BIND_CALL_DIAGNOSTIC_SERVICE">
54 * <intent-filter>
55 * <action android:name="android.telecom.CallDiagnosticService"/>
56 * </intent-filter>
57 * </service>
58 * }
59 * </pre>
Tyler Gunn80196212021-03-11 22:43:09 -080060 * <p>
61 * <h2>Threading Model</h2>
62 * By default, all incoming IPC from Telecom in this service and in the {@link DiagnosticCall}
63 * instances will take place on the main thread. You can override {@link #getExecutor()} in your
64 * implementation to provide your own {@link Executor}.
Tyler Gunnd5821842021-02-05 11:12:57 -080065 * @hide
66 */
67@SystemApi
68public abstract class CallDiagnosticService extends Service {
69
70 /**
71 * Binder stub implementation which handles incoming requests from Telecom.
72 */
73 private final class CallDiagnosticServiceBinder extends ICallDiagnosticService.Stub {
74
75 @Override
76 public void setAdapter(ICallDiagnosticServiceAdapter adapter) throws RemoteException {
77 handleSetAdapter(adapter);
78 }
79
80 @Override
81 public void initializeDiagnosticCall(ParcelableCall call) throws RemoteException {
82 handleCallAdded(call);
83 }
84
85 @Override
86 public void updateCall(ParcelableCall call) throws RemoteException {
87 handleCallUpdated(call);
88 }
89
90 @Override
91 public void removeDiagnosticCall(String callId) throws RemoteException {
92 handleCallRemoved(callId);
93 }
94
95 @Override
96 public void updateCallAudioState(CallAudioState callAudioState) throws RemoteException {
Tyler Gunn80196212021-03-11 22:43:09 -080097 getExecutor().execute(() -> onCallAudioStateChanged(callAudioState));
Tyler Gunnd5821842021-02-05 11:12:57 -080098 }
99
100 @Override
101 public void receiveDeviceToDeviceMessage(String callId, int message, int value) {
102 handleReceivedD2DMessage(callId, message, value);
103 }
104
105 @Override
106 public void receiveBluetoothCallQualityReport(BluetoothCallQualityReport qualityReport)
107 throws RemoteException {
108 handleBluetoothCallQualityReport(qualityReport);
109 }
Tyler Gunnbc9ecbc2021-03-09 15:06:30 -0800110
111 @Override
112 public void notifyCallDisconnected(@NonNull String callId,
113 @NonNull DisconnectCause disconnectCause) throws RemoteException {
114 handleCallDisconnected(callId, disconnectCause);
115 }
Tyler Gunnd5821842021-02-05 11:12:57 -0800116 }
117
118 /**
119 * Listens to events raised by a {@link DiagnosticCall}.
120 */
121 private android.telecom.DiagnosticCall.Listener mDiagnosticCallListener =
122 new android.telecom.DiagnosticCall.Listener() {
123
124 @Override
125 public void onSendDeviceToDeviceMessage(DiagnosticCall diagnosticCall,
126 @DiagnosticCall.MessageType int message, int value) {
127 handleSendDeviceToDeviceMessage(diagnosticCall, message, value);
128 }
129
130 @Override
131 public void onDisplayDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId,
132 CharSequence message) {
133 handleDisplayDiagnosticMessage(diagnosticCall, messageId, message);
134 }
135
136 @Override
137 public void onClearDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId) {
138 handleClearDiagnosticMessage(diagnosticCall, messageId);
139 }
140 };
141
142 /**
143 * The {@link Intent} that must be declared as handled by the service.
144 */
145 @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
146 public static final String SERVICE_INTERFACE = "android.telecom.CallDiagnosticService";
147
148 /**
149 * Map which tracks the Telecom calls received from the Telecom stack.
150 */
151 private final Map<String, Call.Details> mCallByTelecomCallId = new ArrayMap<>();
152 private final Map<String, DiagnosticCall> mDiagnosticCallByTelecomCallId = new ArrayMap<>();
Tyler Gunn80196212021-03-11 22:43:09 -0800153 private final Object mLock = new Object();
Tyler Gunnd5821842021-02-05 11:12:57 -0800154 private ICallDiagnosticServiceAdapter mAdapter;
155
Tyler Gunn80196212021-03-11 22:43:09 -0800156 /**
157 * Handles binding to the {@link CallDiagnosticService}.
158 *
159 * @param intent The Intent that was used to bind to this service,
160 * as given to {@link android.content.Context#bindService
161 * Context.bindService}. Note that any extras that were included with
162 * the Intent at that point will <em>not</em> be seen here.
163 * @return
164 */
Tyler Gunnd5821842021-02-05 11:12:57 -0800165 @Nullable
166 @Override
167 public IBinder onBind(@NonNull Intent intent) {
168 Log.i(this, "onBind!");
169 return new CallDiagnosticServiceBinder();
170 }
171
172 /**
Tyler Gunn80196212021-03-11 22:43:09 -0800173 * Returns the {@link Executor} to use for incoming IPS from Telecom into your service
174 * implementation.
175 * <p>
176 * Override this method in your {@link CallDiagnosticService} implementation to provide the
177 * executor you want to use for incoming IPC.
178 *
179 * @return the {@link Executor} to use for incoming IPC from Telecom to
180 * {@link CallDiagnosticService} and {@link DiagnosticCall}.
181 */
182 @SuppressLint("OnNameExpected")
183 @NonNull public Executor getExecutor() {
184 return new HandlerExecutor(Handler.createAsync(getMainLooper()));
185 }
186
187 /**
Tyler Gunnd5821842021-02-05 11:12:57 -0800188 * Telecom calls this method on the {@link CallDiagnosticService} with details about a new call
189 * which was added to Telecom.
190 * <p>
191 * The {@link CallDiagnosticService} returns an implementation of {@link DiagnosticCall} to be
192 * used for the lifespan of this call.
Tyler Gunn80196212021-03-11 22:43:09 -0800193 * <p>
194 * Calls to this method will use the {@link CallDiagnosticService}'s {@link Executor}; see
195 * {@link CallDiagnosticService#getExecutor()} for more information.
Tyler Gunnd5821842021-02-05 11:12:57 -0800196 *
197 * @param call The details of the new call.
198 * @return An instance of {@link DiagnosticCall} which the {@link CallDiagnosticService}
199 * provides to be used for the lifespan of the call.
200 * @throws IllegalArgumentException if a {@code null} {@link DiagnosticCall} is returned.
201 */
202 public abstract @NonNull DiagnosticCall onInitializeDiagnosticCall(@NonNull
203 android.telecom.Call.Details call);
204
205 /**
206 * Telecom calls this method when a previous created {@link DiagnosticCall} is no longer needed.
207 * This happens when Telecom is no longer tracking the call in question.
Tyler Gunn80196212021-03-11 22:43:09 -0800208 * <p>
209 * Calls to this method will use the {@link CallDiagnosticService}'s {@link Executor}; see
210 * {@link CallDiagnosticService#getExecutor()} for more information.
211 *
Tyler Gunnd5821842021-02-05 11:12:57 -0800212 * @param call The diagnostic call which is no longer tracked by Telecom.
213 */
214 public abstract void onRemoveDiagnosticCall(@NonNull DiagnosticCall call);
215
216 /**
217 * Telecom calls this method when the audio routing or available audio route information
218 * changes.
219 * <p>
220 * Audio state is common to all calls.
Tyler Gunn80196212021-03-11 22:43:09 -0800221 * <p>
222 * Calls to this method will use the {@link CallDiagnosticService}'s {@link Executor}; see
223 * {@link CallDiagnosticService#getExecutor()} for more information.
Tyler Gunnd5821842021-02-05 11:12:57 -0800224 *
225 * @param audioState The new audio state.
226 */
227 public abstract void onCallAudioStateChanged(
228 @NonNull CallAudioState audioState);
229
230 /**
231 * Telecom calls this method when a {@link BluetoothCallQualityReport} is received from the
232 * bluetooth stack.
Tyler Gunn80196212021-03-11 22:43:09 -0800233 * <p>
234 * Calls to this method will use the {@link CallDiagnosticService}'s {@link Executor}; see
235 * {@link CallDiagnosticService#getExecutor()} for more information.
236 *
Tyler Gunnd5821842021-02-05 11:12:57 -0800237 * @param qualityReport the {@link BluetoothCallQualityReport}.
238 */
239 public abstract void onBluetoothCallQualityReportReceived(
240 @NonNull BluetoothCallQualityReport qualityReport);
241
242 /**
243 * Handles a request from Telecom to set the adapater used to communicate back to Telecom.
244 * @param adapter
245 */
246 private void handleSetAdapter(@NonNull ICallDiagnosticServiceAdapter adapter) {
247 mAdapter = adapter;
248 }
249
250 /**
251 * Handles a request from Telecom to add a new call.
252 * @param parcelableCall
253 */
254 private void handleCallAdded(@NonNull ParcelableCall parcelableCall) {
255 String telecomCallId = parcelableCall.getId();
256 Log.i(this, "handleCallAdded: callId=%s - added", telecomCallId);
257 Call.Details newCallDetails = Call.Details.createFromParcelableCall(parcelableCall);
Tyler Gunn80196212021-03-11 22:43:09 -0800258 synchronized (mLock) {
259 mCallByTelecomCallId.put(telecomCallId, newCallDetails);
Tyler Gunnd5821842021-02-05 11:12:57 -0800260 }
Tyler Gunn80196212021-03-11 22:43:09 -0800261
262 getExecutor().execute(() -> {
263 DiagnosticCall diagnosticCall = onInitializeDiagnosticCall(newCallDetails);
264 if (diagnosticCall == null) {
265 throw new IllegalArgumentException(
266 "A valid DiagnosticCall instance was not provided.");
267 }
268 synchronized (mLock) {
269 diagnosticCall.setListener(mDiagnosticCallListener);
270 diagnosticCall.setCallId(telecomCallId);
271 mDiagnosticCallByTelecomCallId.put(telecomCallId, diagnosticCall);
272 }
273 });
Tyler Gunnd5821842021-02-05 11:12:57 -0800274 }
275
276 /**
277 * Handles an update to {@link Call.Details} notified by Telecom.
278 * Caches the call details and notifies the {@link DiagnosticCall} of the change via
279 * {@link DiagnosticCall#onCallDetailsChanged(Call.Details)}.
280 * @param parcelableCall the new parceled call details from Telecom.
281 */
282 private void handleCallUpdated(@NonNull ParcelableCall parcelableCall) {
283 String telecomCallId = parcelableCall.getId();
284 Log.i(this, "handleCallUpdated: callId=%s - updated", telecomCallId);
285 Call.Details newCallDetails = Call.Details.createFromParcelableCall(parcelableCall);
Tyler Gunn80196212021-03-11 22:43:09 -0800286 DiagnosticCall diagnosticCall;
287 synchronized (mLock) {
288 diagnosticCall = mDiagnosticCallByTelecomCallId.get(telecomCallId);
289 mCallByTelecomCallId.put(telecomCallId, newCallDetails);
290 }
291 getExecutor().execute(() -> diagnosticCall.handleCallUpdated(newCallDetails));
Tyler Gunnd5821842021-02-05 11:12:57 -0800292 }
293
294 /**
295 * Handles a request from Telecom to remove an existing call.
296 * @param telecomCallId
297 */
298 private void handleCallRemoved(@NonNull String telecomCallId) {
299 Log.i(this, "handleCallRemoved: callId=%s - removed", telecomCallId);
300
301 if (mCallByTelecomCallId.containsKey(telecomCallId)) {
302 mCallByTelecomCallId.remove(telecomCallId);
303 }
Tyler Gunn80196212021-03-11 22:43:09 -0800304
305 DiagnosticCall diagnosticCall;
306 synchronized (mLock) {
307 if (mDiagnosticCallByTelecomCallId.containsKey(telecomCallId)) {
308 diagnosticCall = mDiagnosticCallByTelecomCallId.remove(telecomCallId);
309 } else {
310 diagnosticCall = null;
311 }
312 }
313
314 // Inform the service of the removed call.
315 if (diagnosticCall != null) {
316 getExecutor().execute(() -> onRemoveDiagnosticCall(diagnosticCall));
Tyler Gunnd5821842021-02-05 11:12:57 -0800317 }
318 }
319
320 /**
321 * Handles an incoming device to device message received from Telecom. Notifies the
322 * {@link DiagnosticCall} via {@link DiagnosticCall#onReceiveDeviceToDeviceMessage(int, int)}.
323 * @param callId
324 * @param message
325 * @param value
326 */
327 private void handleReceivedD2DMessage(@NonNull String callId, int message, int value) {
328 Log.i(this, "handleReceivedD2DMessage: callId=%s, msg=%d/%d", callId, message, value);
Tyler Gunn80196212021-03-11 22:43:09 -0800329 DiagnosticCall diagnosticCall;
330 synchronized (mLock) {
331 diagnosticCall = mDiagnosticCallByTelecomCallId.get(callId);
332 }
333 if (diagnosticCall != null) {
334 getExecutor().execute(
335 () -> diagnosticCall.onReceiveDeviceToDeviceMessage(message, value));
336 }
Tyler Gunnd5821842021-02-05 11:12:57 -0800337 }
338
339 /**
Tyler Gunnbc9ecbc2021-03-09 15:06:30 -0800340 * Handles a request from the Telecom framework to get a disconnect message from the
341 * {@link CallDiagnosticService}.
342 * @param callId The ID of the call.
343 * @param disconnectCause The telecom disconnect cause.
344 */
345 private void handleCallDisconnected(@NonNull String callId,
346 @NonNull DisconnectCause disconnectCause) {
347 Log.i(this, "handleCallDisconnected: call=%s; cause=%s", callId, disconnectCause);
348 DiagnosticCall diagnosticCall = mDiagnosticCallByTelecomCallId.get(callId);
349 CharSequence message;
350 if (disconnectCause.getImsReasonInfo() != null) {
351 message = diagnosticCall.onCallDisconnected(disconnectCause.getImsReasonInfo());
352 } else {
353 message = diagnosticCall.onCallDisconnected(
354 disconnectCause.getTelephonyDisconnectCause(),
355 disconnectCause.getTelephonyPreciseDisconnectCause());
356 }
357 try {
358 mAdapter.overrideDisconnectMessage(callId, message);
359 } catch (RemoteException e) {
360 Log.w(this, "handleCallDisconnected: call=%s; cause=%s; %s",
361 callId, disconnectCause, e);
362 }
363 }
364
365 /**
Tyler Gunnd5821842021-02-05 11:12:57 -0800366 * Handles an incoming bluetooth call quality report from Telecom. Notifies via
367 * {@link CallDiagnosticService#onBluetoothCallQualityReportReceived(
368 * BluetoothCallQualityReport)}.
369 * @param qualityReport The bluetooth call quality remote.
370 */
371 private void handleBluetoothCallQualityReport(@NonNull BluetoothCallQualityReport
372 qualityReport) {
373 Log.i(this, "handleBluetoothCallQualityReport; report=%s", qualityReport);
Tyler Gunn80196212021-03-11 22:43:09 -0800374 getExecutor().execute(() -> onBluetoothCallQualityReportReceived(qualityReport));
Tyler Gunnd5821842021-02-05 11:12:57 -0800375 }
376
377 /**
378 * Handles a request from a {@link DiagnosticCall} to send a device to device message (received
379 * via {@link DiagnosticCall#sendDeviceToDeviceMessage(int, int)}.
380 * @param diagnosticCall
381 * @param message
382 * @param value
383 */
384 private void handleSendDeviceToDeviceMessage(@NonNull DiagnosticCall diagnosticCall,
385 int message, int value) {
386 String callId = diagnosticCall.getCallId();
387 try {
388 mAdapter.sendDeviceToDeviceMessage(callId, message, value);
389 Log.i(this, "handleSendDeviceToDeviceMessage: call=%s; msg=%d/%d", callId, message,
390 value);
391 } catch (RemoteException e) {
392 Log.w(this, "handleSendDeviceToDeviceMessage: call=%s; msg=%d/%d failed %s",
393 callId, message, value, e);
394 }
395 }
396
397 /**
398 * Handles a request from a {@link DiagnosticCall} to display an in-call diagnostic message.
399 * Originates from {@link DiagnosticCall#displayDiagnosticMessage(int, CharSequence)}.
400 * @param diagnosticCall
401 * @param messageId
402 * @param message
403 */
404 private void handleDisplayDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId,
405 CharSequence message) {
406 String callId = diagnosticCall.getCallId();
407 try {
408 mAdapter.displayDiagnosticMessage(callId, messageId, message);
409 Log.i(this, "handleDisplayDiagnosticMessage: call=%s; msg=%d/%s", callId, messageId,
410 message);
411 } catch (RemoteException e) {
412 Log.w(this, "handleDisplayDiagnosticMessage: call=%s; msg=%d/%s failed %s",
413 callId, messageId, message, e);
414 }
415 }
416
417 /**
418 * Handles a request from a {@link DiagnosticCall} to clear a previously shown diagnostic
419 * message.
420 * Originates from {@link DiagnosticCall#clearDiagnosticMessage(int)}.
421 * @param diagnosticCall
422 * @param messageId
423 */
424 private void handleClearDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId) {
425 String callId = diagnosticCall.getCallId();
426 try {
427 mAdapter.clearDiagnosticMessage(callId, messageId);
428 Log.i(this, "handleClearDiagnosticMessage: call=%s; msg=%d", callId, messageId);
429 } catch (RemoteException e) {
430 Log.w(this, "handleClearDiagnosticMessage: call=%s; msg=%d failed %s",
431 callId, messageId, e);
432 }
433 }
434}