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