blob: 809f2bc1bb7d66e98e007c4e5f5fb3a04328e551 [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;
30import android.util.ArrayMap;
31
32import com.android.internal.telecom.ICallDiagnosticService;
33import com.android.internal.telecom.ICallDiagnosticServiceAdapter;
34
35import java.util.Map;
Tyler Gunn80196212021-03-11 22:43:09 -080036import java.util.concurrent.Executor;
Tyler Gunnd5821842021-02-05 11:12:57 -080037
38/**
39 * The platform supports a single OEM provided {@link CallDiagnosticService}, as defined by the
40 * {@code call_diagnostic_service_package_name} key in the
41 * {@code packages/services/Telecomm/res/values/config.xml} file. An OEM can use this API to help
42 * provide more actionable information about calling issues the user encounters during and after
43 * a call.
44 *
45 * <h1>Manifest Declaration</h1>
46 * The following is an example of how to declare the service entry in the
47 * {@link CallDiagnosticService} manifest file:
48 * <pre>
49 * {@code
50 * <service android:name="your.package.YourCallDiagnosticServiceImplementation"
51 * android:permission="android.permission.BIND_CALL_DIAGNOSTIC_SERVICE">
52 * <intent-filter>
53 * <action android:name="android.telecom.CallDiagnosticService"/>
54 * </intent-filter>
55 * </service>
56 * }
57 * </pre>
Tyler Gunn80196212021-03-11 22:43:09 -080058 * <p>
59 * <h2>Threading Model</h2>
60 * By default, all incoming IPC from Telecom in this service and in the {@link DiagnosticCall}
61 * instances will take place on the main thread. You can override {@link #getExecutor()} in your
62 * implementation to provide your own {@link Executor}.
Tyler Gunnd5821842021-02-05 11:12:57 -080063 * @hide
64 */
65@SystemApi
66public abstract class CallDiagnosticService extends Service {
67
68 /**
69 * Binder stub implementation which handles incoming requests from Telecom.
70 */
71 private final class CallDiagnosticServiceBinder extends ICallDiagnosticService.Stub {
72
73 @Override
74 public void setAdapter(ICallDiagnosticServiceAdapter adapter) throws RemoteException {
75 handleSetAdapter(adapter);
76 }
77
78 @Override
79 public void initializeDiagnosticCall(ParcelableCall call) throws RemoteException {
80 handleCallAdded(call);
81 }
82
83 @Override
84 public void updateCall(ParcelableCall call) throws RemoteException {
85 handleCallUpdated(call);
86 }
87
88 @Override
89 public void removeDiagnosticCall(String callId) throws RemoteException {
90 handleCallRemoved(callId);
91 }
92
93 @Override
94 public void updateCallAudioState(CallAudioState callAudioState) throws RemoteException {
Tyler Gunn80196212021-03-11 22:43:09 -080095 getExecutor().execute(() -> onCallAudioStateChanged(callAudioState));
Tyler Gunnd5821842021-02-05 11:12:57 -080096 }
97
98 @Override
99 public void receiveDeviceToDeviceMessage(String callId, int message, int value) {
100 handleReceivedD2DMessage(callId, message, value);
101 }
102
103 @Override
104 public void receiveBluetoothCallQualityReport(BluetoothCallQualityReport qualityReport)
105 throws RemoteException {
106 handleBluetoothCallQualityReport(qualityReport);
107 }
108 }
109
110 /**
111 * Listens to events raised by a {@link DiagnosticCall}.
112 */
113 private android.telecom.DiagnosticCall.Listener mDiagnosticCallListener =
114 new android.telecom.DiagnosticCall.Listener() {
115
116 @Override
117 public void onSendDeviceToDeviceMessage(DiagnosticCall diagnosticCall,
118 @DiagnosticCall.MessageType int message, int value) {
119 handleSendDeviceToDeviceMessage(diagnosticCall, message, value);
120 }
121
122 @Override
123 public void onDisplayDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId,
124 CharSequence message) {
125 handleDisplayDiagnosticMessage(diagnosticCall, messageId, message);
126 }
127
128 @Override
129 public void onClearDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId) {
130 handleClearDiagnosticMessage(diagnosticCall, messageId);
131 }
132 };
133
134 /**
135 * The {@link Intent} that must be declared as handled by the service.
136 */
137 @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
138 public static final String SERVICE_INTERFACE = "android.telecom.CallDiagnosticService";
139
140 /**
141 * Map which tracks the Telecom calls received from the Telecom stack.
142 */
143 private final Map<String, Call.Details> mCallByTelecomCallId = new ArrayMap<>();
144 private final Map<String, DiagnosticCall> mDiagnosticCallByTelecomCallId = new ArrayMap<>();
Tyler Gunn80196212021-03-11 22:43:09 -0800145 private final Object mLock = new Object();
Tyler Gunnd5821842021-02-05 11:12:57 -0800146 private ICallDiagnosticServiceAdapter mAdapter;
147
Tyler Gunn80196212021-03-11 22:43:09 -0800148 /**
149 * Handles binding to the {@link CallDiagnosticService}.
150 *
151 * @param intent The Intent that was used to bind to this service,
152 * as given to {@link android.content.Context#bindService
153 * Context.bindService}. Note that any extras that were included with
154 * the Intent at that point will <em>not</em> be seen here.
155 * @return
156 */
Tyler Gunnd5821842021-02-05 11:12:57 -0800157 @Nullable
158 @Override
159 public IBinder onBind(@NonNull Intent intent) {
160 Log.i(this, "onBind!");
161 return new CallDiagnosticServiceBinder();
162 }
163
164 /**
Tyler Gunn80196212021-03-11 22:43:09 -0800165 * Returns the {@link Executor} to use for incoming IPS from Telecom into your service
166 * implementation.
167 * <p>
168 * Override this method in your {@link CallDiagnosticService} implementation to provide the
169 * executor you want to use for incoming IPC.
170 *
171 * @return the {@link Executor} to use for incoming IPC from Telecom to
172 * {@link CallDiagnosticService} and {@link DiagnosticCall}.
173 */
174 @SuppressLint("OnNameExpected")
175 @NonNull public Executor getExecutor() {
176 return new HandlerExecutor(Handler.createAsync(getMainLooper()));
177 }
178
179 /**
Tyler Gunnd5821842021-02-05 11:12:57 -0800180 * Telecom calls this method on the {@link CallDiagnosticService} with details about a new call
181 * which was added to Telecom.
182 * <p>
183 * The {@link CallDiagnosticService} returns an implementation of {@link DiagnosticCall} to be
184 * used for the lifespan of this call.
Tyler Gunn80196212021-03-11 22:43:09 -0800185 * <p>
186 * Calls to this method will use the {@link CallDiagnosticService}'s {@link Executor}; see
187 * {@link CallDiagnosticService#getExecutor()} for more information.
Tyler Gunnd5821842021-02-05 11:12:57 -0800188 *
189 * @param call The details of the new call.
190 * @return An instance of {@link DiagnosticCall} which the {@link CallDiagnosticService}
191 * provides to be used for the lifespan of the call.
192 * @throws IllegalArgumentException if a {@code null} {@link DiagnosticCall} is returned.
193 */
194 public abstract @NonNull DiagnosticCall onInitializeDiagnosticCall(@NonNull
195 android.telecom.Call.Details call);
196
197 /**
198 * Telecom calls this method when a previous created {@link DiagnosticCall} is no longer needed.
199 * This happens when Telecom is no longer tracking the call in question.
Tyler Gunn80196212021-03-11 22:43:09 -0800200 * <p>
201 * Calls to this method will use the {@link CallDiagnosticService}'s {@link Executor}; see
202 * {@link CallDiagnosticService#getExecutor()} for more information.
203 *
Tyler Gunnd5821842021-02-05 11:12:57 -0800204 * @param call The diagnostic call which is no longer tracked by Telecom.
205 */
206 public abstract void onRemoveDiagnosticCall(@NonNull DiagnosticCall call);
207
208 /**
209 * Telecom calls this method when the audio routing or available audio route information
210 * changes.
211 * <p>
212 * Audio state is common to all calls.
Tyler Gunn80196212021-03-11 22:43:09 -0800213 * <p>
214 * Calls to this method will use the {@link CallDiagnosticService}'s {@link Executor}; see
215 * {@link CallDiagnosticService#getExecutor()} for more information.
Tyler Gunnd5821842021-02-05 11:12:57 -0800216 *
217 * @param audioState The new audio state.
218 */
219 public abstract void onCallAudioStateChanged(
220 @NonNull CallAudioState audioState);
221
222 /**
223 * Telecom calls this method when a {@link BluetoothCallQualityReport} is received from the
224 * bluetooth stack.
Tyler Gunn80196212021-03-11 22:43:09 -0800225 * <p>
226 * Calls to this method will use the {@link CallDiagnosticService}'s {@link Executor}; see
227 * {@link CallDiagnosticService#getExecutor()} for more information.
228 *
Tyler Gunnd5821842021-02-05 11:12:57 -0800229 * @param qualityReport the {@link BluetoothCallQualityReport}.
230 */
231 public abstract void onBluetoothCallQualityReportReceived(
232 @NonNull BluetoothCallQualityReport qualityReport);
233
234 /**
235 * Handles a request from Telecom to set the adapater used to communicate back to Telecom.
236 * @param adapter
237 */
238 private void handleSetAdapter(@NonNull ICallDiagnosticServiceAdapter adapter) {
239 mAdapter = adapter;
240 }
241
242 /**
243 * Handles a request from Telecom to add a new call.
244 * @param parcelableCall
245 */
246 private void handleCallAdded(@NonNull ParcelableCall parcelableCall) {
247 String telecomCallId = parcelableCall.getId();
248 Log.i(this, "handleCallAdded: callId=%s - added", telecomCallId);
249 Call.Details newCallDetails = Call.Details.createFromParcelableCall(parcelableCall);
Tyler Gunn80196212021-03-11 22:43:09 -0800250 synchronized (mLock) {
251 mCallByTelecomCallId.put(telecomCallId, newCallDetails);
Tyler Gunnd5821842021-02-05 11:12:57 -0800252 }
Tyler Gunn80196212021-03-11 22:43:09 -0800253
254 getExecutor().execute(() -> {
255 DiagnosticCall diagnosticCall = onInitializeDiagnosticCall(newCallDetails);
256 if (diagnosticCall == null) {
257 throw new IllegalArgumentException(
258 "A valid DiagnosticCall instance was not provided.");
259 }
260 synchronized (mLock) {
261 diagnosticCall.setListener(mDiagnosticCallListener);
262 diagnosticCall.setCallId(telecomCallId);
263 mDiagnosticCallByTelecomCallId.put(telecomCallId, diagnosticCall);
264 }
265 });
Tyler Gunnd5821842021-02-05 11:12:57 -0800266 }
267
268 /**
269 * Handles an update to {@link Call.Details} notified by Telecom.
270 * Caches the call details and notifies the {@link DiagnosticCall} of the change via
271 * {@link DiagnosticCall#onCallDetailsChanged(Call.Details)}.
272 * @param parcelableCall the new parceled call details from Telecom.
273 */
274 private void handleCallUpdated(@NonNull ParcelableCall parcelableCall) {
275 String telecomCallId = parcelableCall.getId();
276 Log.i(this, "handleCallUpdated: callId=%s - updated", telecomCallId);
277 Call.Details newCallDetails = Call.Details.createFromParcelableCall(parcelableCall);
Tyler Gunn80196212021-03-11 22:43:09 -0800278 DiagnosticCall diagnosticCall;
279 synchronized (mLock) {
280 diagnosticCall = mDiagnosticCallByTelecomCallId.get(telecomCallId);
281 mCallByTelecomCallId.put(telecomCallId, newCallDetails);
282 }
283 getExecutor().execute(() -> diagnosticCall.handleCallUpdated(newCallDetails));
Tyler Gunnd5821842021-02-05 11:12:57 -0800284 }
285
286 /**
287 * Handles a request from Telecom to remove an existing call.
288 * @param telecomCallId
289 */
290 private void handleCallRemoved(@NonNull String telecomCallId) {
291 Log.i(this, "handleCallRemoved: callId=%s - removed", telecomCallId);
292
293 if (mCallByTelecomCallId.containsKey(telecomCallId)) {
294 mCallByTelecomCallId.remove(telecomCallId);
295 }
Tyler Gunn80196212021-03-11 22:43:09 -0800296
297 DiagnosticCall diagnosticCall;
298 synchronized (mLock) {
299 if (mDiagnosticCallByTelecomCallId.containsKey(telecomCallId)) {
300 diagnosticCall = mDiagnosticCallByTelecomCallId.remove(telecomCallId);
301 } else {
302 diagnosticCall = null;
303 }
304 }
305
306 // Inform the service of the removed call.
307 if (diagnosticCall != null) {
308 getExecutor().execute(() -> onRemoveDiagnosticCall(diagnosticCall));
Tyler Gunnd5821842021-02-05 11:12:57 -0800309 }
310 }
311
312 /**
313 * Handles an incoming device to device message received from Telecom. Notifies the
314 * {@link DiagnosticCall} via {@link DiagnosticCall#onReceiveDeviceToDeviceMessage(int, int)}.
315 * @param callId
316 * @param message
317 * @param value
318 */
319 private void handleReceivedD2DMessage(@NonNull String callId, int message, int value) {
320 Log.i(this, "handleReceivedD2DMessage: callId=%s, msg=%d/%d", callId, message, value);
Tyler Gunn80196212021-03-11 22:43:09 -0800321 DiagnosticCall diagnosticCall;
322 synchronized (mLock) {
323 diagnosticCall = mDiagnosticCallByTelecomCallId.get(callId);
324 }
325 if (diagnosticCall != null) {
326 getExecutor().execute(
327 () -> diagnosticCall.onReceiveDeviceToDeviceMessage(message, value));
328 }
Tyler Gunnd5821842021-02-05 11:12:57 -0800329 }
330
331 /**
332 * Handles an incoming bluetooth call quality report from Telecom. Notifies via
333 * {@link CallDiagnosticService#onBluetoothCallQualityReportReceived(
334 * BluetoothCallQualityReport)}.
335 * @param qualityReport The bluetooth call quality remote.
336 */
337 private void handleBluetoothCallQualityReport(@NonNull BluetoothCallQualityReport
338 qualityReport) {
339 Log.i(this, "handleBluetoothCallQualityReport; report=%s", qualityReport);
Tyler Gunn80196212021-03-11 22:43:09 -0800340 getExecutor().execute(() -> onBluetoothCallQualityReportReceived(qualityReport));
Tyler Gunnd5821842021-02-05 11:12:57 -0800341 }
342
343 /**
344 * Handles a request from a {@link DiagnosticCall} to send a device to device message (received
345 * via {@link DiagnosticCall#sendDeviceToDeviceMessage(int, int)}.
346 * @param diagnosticCall
347 * @param message
348 * @param value
349 */
350 private void handleSendDeviceToDeviceMessage(@NonNull DiagnosticCall diagnosticCall,
351 int message, int value) {
352 String callId = diagnosticCall.getCallId();
353 try {
354 mAdapter.sendDeviceToDeviceMessage(callId, message, value);
355 Log.i(this, "handleSendDeviceToDeviceMessage: call=%s; msg=%d/%d", callId, message,
356 value);
357 } catch (RemoteException e) {
358 Log.w(this, "handleSendDeviceToDeviceMessage: call=%s; msg=%d/%d failed %s",
359 callId, message, value, e);
360 }
361 }
362
363 /**
364 * Handles a request from a {@link DiagnosticCall} to display an in-call diagnostic message.
365 * Originates from {@link DiagnosticCall#displayDiagnosticMessage(int, CharSequence)}.
366 * @param diagnosticCall
367 * @param messageId
368 * @param message
369 */
370 private void handleDisplayDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId,
371 CharSequence message) {
372 String callId = diagnosticCall.getCallId();
373 try {
374 mAdapter.displayDiagnosticMessage(callId, messageId, message);
375 Log.i(this, "handleDisplayDiagnosticMessage: call=%s; msg=%d/%s", callId, messageId,
376 message);
377 } catch (RemoteException e) {
378 Log.w(this, "handleDisplayDiagnosticMessage: call=%s; msg=%d/%s failed %s",
379 callId, messageId, message, e);
380 }
381 }
382
383 /**
384 * Handles a request from a {@link DiagnosticCall} to clear a previously shown diagnostic
385 * message.
386 * Originates from {@link DiagnosticCall#clearDiagnosticMessage(int)}.
387 * @param diagnosticCall
388 * @param messageId
389 */
390 private void handleClearDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId) {
391 String callId = diagnosticCall.getCallId();
392 try {
393 mAdapter.clearDiagnosticMessage(callId, messageId);
394 Log.i(this, "handleClearDiagnosticMessage: call=%s; msg=%d", callId, messageId);
395 } catch (RemoteException e) {
396 Log.w(this, "handleClearDiagnosticMessage: call=%s; msg=%d failed %s",
397 callId, messageId, e);
398 }
399 }
400}