467 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Java
		
	
	
	
			
		
		
	
	
			467 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Java
		
	
	
	
| /*
 | |
|  * Copyright (C) 2021 The Android Open Source Project
 | |
|  *
 | |
|  * Licensed under the Apache License, Version 2.0 (the "License");
 | |
|  * you may not use this file except in compliance with the License.
 | |
|  * You may obtain a copy of the License at
 | |
|  *
 | |
|  *      http://www.apache.org/licenses/LICENSE-2.0
 | |
|  *
 | |
|  * Unless required by applicable law or agreed to in writing, software
 | |
|  * distributed under the License is distributed on an "AS IS" BASIS,
 | |
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
|  * See the License for the specific language governing permissions and
 | |
|  * limitations under the License.
 | |
|  */
 | |
| 
 | |
| package android.telecom;
 | |
| 
 | |
| import android.annotation.NonNull;
 | |
| import android.annotation.Nullable;
 | |
| import android.annotation.SdkConstant;
 | |
| import android.annotation.SuppressLint;
 | |
| import android.annotation.SystemApi;
 | |
| import android.app.Service;
 | |
| import android.content.Intent;
 | |
| import android.os.Handler;
 | |
| import android.os.HandlerExecutor;
 | |
| import android.os.IBinder;
 | |
| import android.os.RemoteException;
 | |
| 
 | |
| import android.telephony.CallQuality;
 | |
| import android.util.ArrayMap;
 | |
| 
 | |
| import com.android.internal.telecom.ICallDiagnosticService;
 | |
| import com.android.internal.telecom.ICallDiagnosticServiceAdapter;
 | |
| 
 | |
| import java.util.Map;
 | |
| import java.util.concurrent.Executor;
 | |
| 
 | |
| /**
 | |
|  * The platform supports a single OEM provided {@link CallDiagnosticService}, as defined by the
 | |
|  * {@code call_diagnostic_service_package_name} key in the
 | |
|  * {@code packages/services/Telecomm/res/values/config.xml} file.  An OEM can use this API to help
 | |
|  * provide more actionable information about calling issues the user encounters during and after
 | |
|  * a call.
 | |
|  *
 | |
|  * <h1>Manifest Declaration</h1>
 | |
|  * The following is an example of how to declare the service entry in the
 | |
|  * {@link CallDiagnosticService} manifest file:
 | |
|  * <pre>
 | |
|  * {@code
 | |
|  * <service android:name="your.package.YourCallDiagnosticServiceImplementation"
 | |
|  *          android:permission="android.permission.BIND_CALL_DIAGNOSTIC_SERVICE">
 | |
|  *      <intent-filter>
 | |
|  *          <action android:name="android.telecom.CallDiagnosticService"/>
 | |
|  *      </intent-filter>
 | |
|  * </service>
 | |
|  * }
 | |
|  * </pre>
 | |
|  * <p>
 | |
|  * <h2>Threading Model</h2>
 | |
|  * By default, all incoming IPC from Telecom in this service and in the {@link CallDiagnostics}
 | |
|  * instances will take place on the main thread.  You can override {@link #getExecutor()} in your
 | |
|  * implementation to provide your own {@link Executor}.
 | |
|  * @hide
 | |
|  */
 | |
| @SystemApi
 | |
| public abstract class CallDiagnosticService extends Service {
 | |
| 
 | |
|     /**
 | |
|      * Binder stub implementation which handles incoming requests from Telecom.
 | |
|      */
 | |
|     private final class CallDiagnosticServiceBinder extends ICallDiagnosticService.Stub {
 | |
| 
 | |
|         @Override
 | |
|         public void setAdapter(ICallDiagnosticServiceAdapter adapter) throws RemoteException {
 | |
|             handleSetAdapter(adapter);
 | |
|         }
 | |
| 
 | |
|         @Override
 | |
|         public void initializeDiagnosticCall(ParcelableCall call) throws RemoteException {
 | |
|             handleCallAdded(call);
 | |
|         }
 | |
| 
 | |
|         @Override
 | |
|         public void updateCall(ParcelableCall call) throws RemoteException {
 | |
|             handleCallUpdated(call);
 | |
|         }
 | |
| 
 | |
|         @Override
 | |
|         public void removeDiagnosticCall(String callId) throws RemoteException {
 | |
|             handleCallRemoved(callId);
 | |
|         }
 | |
| 
 | |
|         @Override
 | |
|         public void updateCallAudioState(CallAudioState callAudioState) throws RemoteException {
 | |
|             getExecutor().execute(() -> onCallAudioStateChanged(callAudioState));
 | |
|         }
 | |
| 
 | |
|         @Override
 | |
|         public void receiveDeviceToDeviceMessage(String callId, int message, int value) {
 | |
|             handleReceivedD2DMessage(callId, message, value);
 | |
|         }
 | |
| 
 | |
|         @Override
 | |
|         public void receiveBluetoothCallQualityReport(BluetoothCallQualityReport qualityReport)
 | |
|                 throws RemoteException {
 | |
|             handleBluetoothCallQualityReport(qualityReport);
 | |
|         }
 | |
| 
 | |
|         @Override
 | |
|         public void notifyCallDisconnected(@NonNull String callId,
 | |
|                 @NonNull DisconnectCause disconnectCause) throws RemoteException {
 | |
|             handleCallDisconnected(callId, disconnectCause);
 | |
|         }
 | |
| 
 | |
|         @Override
 | |
|         public void callQualityChanged(String callId, CallQuality callQuality)
 | |
|                 throws RemoteException {
 | |
|             handleCallQualityChanged(callId, callQuality);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Listens to events raised by a {@link CallDiagnostics}.
 | |
|      */
 | |
|     private CallDiagnostics.Listener mDiagnosticCallListener =
 | |
|             new CallDiagnostics.Listener() {
 | |
| 
 | |
|                 @Override
 | |
|                 public void onSendDeviceToDeviceMessage(CallDiagnostics callDiagnostics,
 | |
|                         @CallDiagnostics.MessageType int message, int value) {
 | |
|                     handleSendDeviceToDeviceMessage(callDiagnostics, message, value);
 | |
|                 }
 | |
| 
 | |
|                 @Override
 | |
|                 public void onDisplayDiagnosticMessage(CallDiagnostics callDiagnostics,
 | |
|                         int messageId,
 | |
|                         CharSequence message) {
 | |
|                     handleDisplayDiagnosticMessage(callDiagnostics, messageId, message);
 | |
|                 }
 | |
| 
 | |
|                 @Override
 | |
|                 public void onClearDiagnosticMessage(CallDiagnostics callDiagnostics,
 | |
|                         int messageId) {
 | |
|                     handleClearDiagnosticMessage(callDiagnostics, messageId);
 | |
|                 }
 | |
|             };
 | |
| 
 | |
|     /**
 | |
|      * The {@link Intent} that must be declared as handled by the service.
 | |
|      */
 | |
|     @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
 | |
|     public static final String SERVICE_INTERFACE = "android.telecom.CallDiagnosticService";
 | |
| 
 | |
|     /**
 | |
|      * Map which tracks the Telecom calls received from the Telecom stack.
 | |
|      */
 | |
|     private final Map<String, Call.Details> mCallByTelecomCallId = new ArrayMap<>();
 | |
|     private final Map<String, CallDiagnostics> mDiagnosticCallByTelecomCallId = new ArrayMap<>();
 | |
|     private final Object mLock = new Object();
 | |
|     private ICallDiagnosticServiceAdapter mAdapter;
 | |
| 
 | |
|     /**
 | |
|      * Handles binding to the {@link CallDiagnosticService}.
 | |
|      *
 | |
|      * @param intent The Intent that was used to bind to this service,
 | |
|      * as given to {@link android.content.Context#bindService
 | |
|      * Context.bindService}.  Note that any extras that were included with
 | |
|      * the Intent at that point will <em>not</em> be seen here.
 | |
|      * @return
 | |
|      */
 | |
|     @Nullable
 | |
|     @Override
 | |
|     public IBinder onBind(@NonNull Intent intent) {
 | |
|         Log.i(this, "onBind!");
 | |
|         return new CallDiagnosticServiceBinder();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns the {@link Executor} to use for incoming IPS from Telecom into your service
 | |
|      * implementation.
 | |
|      * <p>
 | |
|      * Override this method in your {@link CallDiagnosticService} implementation to provide the
 | |
|      * executor you want to use for incoming IPC.
 | |
|      *
 | |
|      * @return the {@link Executor} to use for incoming IPC from Telecom to
 | |
|      * {@link CallDiagnosticService} and {@link CallDiagnostics}.
 | |
|      */
 | |
|     @SuppressLint("OnNameExpected")
 | |
|     @NonNull public Executor getExecutor() {
 | |
|         return new HandlerExecutor(Handler.createAsync(getMainLooper()));
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Telecom calls this method on the {@link CallDiagnosticService} with details about a new call
 | |
|      * which was added to Telecom.
 | |
|      * <p>
 | |
|      * The {@link CallDiagnosticService} returns an implementation of {@link CallDiagnostics} to be
 | |
|      * used for the lifespan of this call.
 | |
|      * <p>
 | |
|      * Calls to this method will use the {@link CallDiagnosticService}'s {@link Executor}; see
 | |
|      * {@link CallDiagnosticService#getExecutor()} for more information.
 | |
|      *
 | |
|      * @param call The details of the new call.
 | |
|      * @return An instance of {@link CallDiagnostics} which the {@link CallDiagnosticService}
 | |
|      * provides to be used for the lifespan of the call.
 | |
|      * @throws IllegalArgumentException if a {@code null} {@link CallDiagnostics} is returned.
 | |
|      */
 | |
|     public abstract @NonNull CallDiagnostics onInitializeCallDiagnostics(@NonNull
 | |
|             android.telecom.Call.Details call);
 | |
| 
 | |
|     /**
 | |
|      * Telecom calls this method when a previous created {@link CallDiagnostics} is no longer
 | |
|      * needed.  This happens when Telecom is no longer tracking the call in question.
 | |
|      * <p>
 | |
|      * Calls to this method will use the {@link CallDiagnosticService}'s {@link Executor}; see
 | |
|      * {@link CallDiagnosticService#getExecutor()} for more information.
 | |
|      *
 | |
|      * @param call The diagnostic call which is no longer tracked by Telecom.
 | |
|      */
 | |
|     public abstract void onRemoveCallDiagnostics(@NonNull CallDiagnostics call);
 | |
| 
 | |
|     /**
 | |
|      * Telecom calls this method when the audio routing or available audio route information
 | |
|      * changes.
 | |
|      * <p>
 | |
|      * Audio state is common to all calls.
 | |
|      * <p>
 | |
|      * Calls to this method will use the {@link CallDiagnosticService}'s {@link Executor}; see
 | |
|      * {@link CallDiagnosticService#getExecutor()} for more information.
 | |
|      *
 | |
|      * @param audioState The new audio state.
 | |
|      */
 | |
|     public abstract void onCallAudioStateChanged(
 | |
|             @NonNull CallAudioState audioState);
 | |
| 
 | |
|     /**
 | |
|      * Telecom calls this method when a {@link BluetoothCallQualityReport} is received from the
 | |
|      * bluetooth stack.
 | |
|      * <p>
 | |
|      * Calls to this method will use the {@link CallDiagnosticService}'s {@link Executor}; see
 | |
|      * {@link CallDiagnosticService#getExecutor()} for more information.
 | |
|      *
 | |
|      * @param qualityReport the {@link BluetoothCallQualityReport}.
 | |
|      */
 | |
|     public abstract void onBluetoothCallQualityReportReceived(
 | |
|             @NonNull BluetoothCallQualityReport qualityReport);
 | |
| 
 | |
|     /**
 | |
|      * Handles a request from Telecom to set the adapater used to communicate back to Telecom.
 | |
|      * @param adapter
 | |
|      */
 | |
|     private void handleSetAdapter(@NonNull ICallDiagnosticServiceAdapter adapter) {
 | |
|         mAdapter = adapter;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Handles a request from Telecom to add a new call.
 | |
|      * @param parcelableCall
 | |
|      */
 | |
|     private void handleCallAdded(@NonNull ParcelableCall parcelableCall) {
 | |
|         String telecomCallId = parcelableCall.getId();
 | |
|         Log.i(this, "handleCallAdded: callId=%s - added", telecomCallId);
 | |
|         Call.Details newCallDetails = Call.Details.createFromParcelableCall(parcelableCall);
 | |
|         synchronized (mLock) {
 | |
|             mCallByTelecomCallId.put(telecomCallId, newCallDetails);
 | |
|         }
 | |
| 
 | |
|         getExecutor().execute(() -> {
 | |
|             CallDiagnostics callDiagnostics = onInitializeCallDiagnostics(newCallDetails);
 | |
|             if (callDiagnostics == null) {
 | |
|                 throw new IllegalArgumentException(
 | |
|                         "A valid DiagnosticCall instance was not provided.");
 | |
|             }
 | |
|             synchronized (mLock) {
 | |
|                 callDiagnostics.setListener(mDiagnosticCallListener);
 | |
|                 callDiagnostics.setCallId(telecomCallId);
 | |
|                 mDiagnosticCallByTelecomCallId.put(telecomCallId, callDiagnostics);
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Handles an update to {@link Call.Details} notified by Telecom.
 | |
|      * Caches the call details and notifies the {@link CallDiagnostics} of the change via
 | |
|      * {@link CallDiagnostics#onCallDetailsChanged(Call.Details)}.
 | |
|      * @param parcelableCall the new parceled call details from Telecom.
 | |
|      */
 | |
|     private void handleCallUpdated(@NonNull ParcelableCall parcelableCall) {
 | |
|         String telecomCallId = parcelableCall.getId();
 | |
|         Log.i(this, "handleCallUpdated: callId=%s - updated", telecomCallId);
 | |
|         Call.Details newCallDetails = Call.Details.createFromParcelableCall(parcelableCall);
 | |
|         CallDiagnostics callDiagnostics;
 | |
|         synchronized (mLock) {
 | |
|             callDiagnostics = mDiagnosticCallByTelecomCallId.get(telecomCallId);
 | |
|             if (callDiagnostics == null) {
 | |
|                 // Possible to get a call update after a call is removed.
 | |
|                 return;
 | |
|             }
 | |
|             mCallByTelecomCallId.put(telecomCallId, newCallDetails);
 | |
|         }
 | |
|         getExecutor().execute(() -> callDiagnostics.handleCallUpdated(newCallDetails));
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Handles a request from Telecom to remove an existing call.
 | |
|      * @param telecomCallId
 | |
|      */
 | |
|     private void handleCallRemoved(@NonNull String telecomCallId) {
 | |
|         Log.i(this, "handleCallRemoved: callId=%s - removed", telecomCallId);
 | |
| 
 | |
|         CallDiagnostics callDiagnostics;
 | |
|         synchronized (mLock) {
 | |
|             if (mCallByTelecomCallId.containsKey(telecomCallId)) {
 | |
|                 mCallByTelecomCallId.remove(telecomCallId);
 | |
|             }
 | |
| 
 | |
|             if (mDiagnosticCallByTelecomCallId.containsKey(telecomCallId)) {
 | |
|                 callDiagnostics = mDiagnosticCallByTelecomCallId.remove(telecomCallId);
 | |
|             } else {
 | |
|                 callDiagnostics = null;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // Inform the service of the removed call.
 | |
|         if (callDiagnostics != null) {
 | |
|             getExecutor().execute(() -> onRemoveCallDiagnostics(callDiagnostics));
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Handles an incoming device to device message received from Telecom.  Notifies the
 | |
|      * {@link CallDiagnostics} via {@link CallDiagnostics#onReceiveDeviceToDeviceMessage(int, int)}.
 | |
|      * @param callId
 | |
|      * @param message
 | |
|      * @param value
 | |
|      */
 | |
|     private void handleReceivedD2DMessage(@NonNull String callId, int message, int value) {
 | |
|         Log.i(this, "handleReceivedD2DMessage: callId=%s, msg=%d/%d", callId, message, value);
 | |
|         CallDiagnostics callDiagnostics;
 | |
|         synchronized (mLock) {
 | |
|             callDiagnostics = mDiagnosticCallByTelecomCallId.get(callId);
 | |
|         }
 | |
|         if (callDiagnostics != null) {
 | |
|             getExecutor().execute(
 | |
|                     () -> callDiagnostics.onReceiveDeviceToDeviceMessage(message, value));
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Handles a request from the Telecom framework to get a disconnect message from the
 | |
|      * {@link CallDiagnosticService}.
 | |
|      * @param callId The ID of the call.
 | |
|      * @param disconnectCause The telecom disconnect cause.
 | |
|      */
 | |
|     private void handleCallDisconnected(@NonNull String callId,
 | |
|             @NonNull DisconnectCause disconnectCause) {
 | |
|         Log.i(this, "handleCallDisconnected: call=%s; cause=%s", callId, disconnectCause);
 | |
|         CallDiagnostics callDiagnostics;
 | |
|         synchronized (mLock) {
 | |
|             callDiagnostics = mDiagnosticCallByTelecomCallId.get(callId);
 | |
|         }
 | |
|         CharSequence message;
 | |
|         if (disconnectCause.getImsReasonInfo() != null) {
 | |
|             message = callDiagnostics.onCallDisconnected(disconnectCause.getImsReasonInfo());
 | |
|         } else {
 | |
|             message = callDiagnostics.onCallDisconnected(
 | |
|                     disconnectCause.getTelephonyDisconnectCause(),
 | |
|                     disconnectCause.getTelephonyPreciseDisconnectCause());
 | |
|         }
 | |
|         try {
 | |
|             mAdapter.overrideDisconnectMessage(callId, message);
 | |
|         } catch (RemoteException e) {
 | |
|             Log.w(this, "handleCallDisconnected: call=%s; cause=%s; %s",
 | |
|                     callId, disconnectCause, e);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Handles an incoming bluetooth call quality report from Telecom.  Notifies via
 | |
|      * {@link CallDiagnosticService#onBluetoothCallQualityReportReceived(
 | |
|      * BluetoothCallQualityReport)}.
 | |
|      * @param qualityReport The bluetooth call quality remote.
 | |
|      */
 | |
|     private void handleBluetoothCallQualityReport(@NonNull BluetoothCallQualityReport
 | |
|             qualityReport) {
 | |
|         Log.i(this, "handleBluetoothCallQualityReport; report=%s", qualityReport);
 | |
|         getExecutor().execute(() -> onBluetoothCallQualityReportReceived(qualityReport));
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Handles a change reported by Telecom to the call quality for a call.
 | |
|      * @param callId the call ID the change applies to.
 | |
|      * @param callQuality The new call quality.
 | |
|      */
 | |
|     private void handleCallQualityChanged(@NonNull String callId,
 | |
|             @NonNull CallQuality callQuality) {
 | |
|         Log.i(this, "handleCallQualityChanged; call=%s, cq=%s", callId, callQuality);
 | |
|         CallDiagnostics callDiagnostics;
 | |
|         synchronized(mLock) {
 | |
|             callDiagnostics = mDiagnosticCallByTelecomCallId.get(callId);
 | |
|         }
 | |
|         if (callDiagnostics != null) {
 | |
|             callDiagnostics.onCallQualityReceived(callQuality);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Handles a request from a {@link CallDiagnostics} to send a device to device message (received
 | |
|      * via {@link CallDiagnostics#sendDeviceToDeviceMessage(int, int)}.
 | |
|      * @param callDiagnostics
 | |
|      * @param message
 | |
|      * @param value
 | |
|      */
 | |
|     private void handleSendDeviceToDeviceMessage(@NonNull CallDiagnostics callDiagnostics,
 | |
|             int message, int value) {
 | |
|         String callId = callDiagnostics.getCallId();
 | |
|         try {
 | |
|             mAdapter.sendDeviceToDeviceMessage(callId, message, value);
 | |
|             Log.i(this, "handleSendDeviceToDeviceMessage: call=%s; msg=%d/%d", callId, message,
 | |
|                     value);
 | |
|         } catch (RemoteException e) {
 | |
|             Log.w(this, "handleSendDeviceToDeviceMessage: call=%s; msg=%d/%d failed %s",
 | |
|                     callId, message, value, e);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Handles a request from a {@link CallDiagnostics} to display an in-call diagnostic message.
 | |
|      * Originates from {@link CallDiagnostics#displayDiagnosticMessage(int, CharSequence)}.
 | |
|      * @param callDiagnostics
 | |
|      * @param messageId
 | |
|      * @param message
 | |
|      */
 | |
|     private void handleDisplayDiagnosticMessage(CallDiagnostics callDiagnostics, int messageId,
 | |
|             CharSequence message) {
 | |
|         String callId = callDiagnostics.getCallId();
 | |
|         try {
 | |
|             mAdapter.displayDiagnosticMessage(callId, messageId, message);
 | |
|             Log.i(this, "handleDisplayDiagnosticMessage: call=%s; msg=%d/%s", callId, messageId,
 | |
|                     message);
 | |
|         } catch (RemoteException e) {
 | |
|             Log.w(this, "handleDisplayDiagnosticMessage: call=%s; msg=%d/%s failed %s",
 | |
|                     callId, messageId, message, e);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Handles a request from a {@link CallDiagnostics} to clear a previously shown diagnostic
 | |
|      * message.
 | |
|      * Originates from {@link CallDiagnostics#clearDiagnosticMessage(int)}.
 | |
|      * @param callDiagnostics
 | |
|      * @param messageId
 | |
|      */
 | |
|     private void handleClearDiagnosticMessage(CallDiagnostics callDiagnostics, int messageId) {
 | |
|         String callId = callDiagnostics.getCallId();
 | |
|         try {
 | |
|             mAdapter.clearDiagnosticMessage(callId, messageId);
 | |
|             Log.i(this, "handleClearDiagnosticMessage: call=%s; msg=%d", callId, messageId);
 | |
|         } catch (RemoteException e) {
 | |
|             Log.w(this, "handleClearDiagnosticMessage: call=%s; msg=%d failed %s",
 | |
|                     callId, messageId, e);
 | |
|         }
 | |
|     }
 | |
| }
 |