Merge "Call Diagnostic Service implementation"
This commit is contained in:
commit
f4f7063e45
|
@ -69,4 +69,8 @@
|
|||
<!-- When true, the options in the call blocking settings to block restricted and unknown
|
||||
callers are combined into a single toggle. -->
|
||||
<bool name="combine_options_to_block_restricted_and_unknown_callers">true</bool>
|
||||
|
||||
<!-- When set, Telecom will attempt to bind to the {@link CallDiagnosticService} implementation
|
||||
defined by the app with this package name. -->
|
||||
<string name="call_diagnostic_service_package_name"></string>
|
||||
</resources>
|
||||
|
|
|
@ -37,11 +37,13 @@ import android.os.Trace;
|
|||
import android.os.UserHandle;
|
||||
import android.provider.CallLog;
|
||||
import android.provider.ContactsContract.Contacts;
|
||||
import android.telecom.BluetoothCallQualityReport;
|
||||
import android.telecom.CallAudioState;
|
||||
import android.telecom.CallerInfo;
|
||||
import android.telecom.Conference;
|
||||
import android.telecom.Connection;
|
||||
import android.telecom.ConnectionService;
|
||||
import android.telecom.DiagnosticCall;
|
||||
import android.telecom.DisconnectCause;
|
||||
import android.telecom.GatewayInfo;
|
||||
import android.telecom.InCallService;
|
||||
|
@ -156,6 +158,8 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
|
|||
Bundle extras, boolean isLegacy);
|
||||
void onHandoverFailed(Call call, int error);
|
||||
void onHandoverComplete(Call call);
|
||||
void onBluetoothCallQualityReport(Call call, BluetoothCallQualityReport report);
|
||||
void onReceivedDeviceToDeviceMessage(Call call, int messageType, int messageValue);
|
||||
}
|
||||
|
||||
public abstract static class ListenerBase implements Listener {
|
||||
|
@ -244,6 +248,10 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
|
|||
public void onHandoverFailed(Call call, int error) {}
|
||||
@Override
|
||||
public void onHandoverComplete(Call call) {}
|
||||
@Override
|
||||
public void onBluetoothCallQualityReport(Call call, BluetoothCallQualityReport report) {}
|
||||
@Override
|
||||
public void onReceivedDeviceToDeviceMessage(Call call, int messageType, int messageValue) {}
|
||||
}
|
||||
|
||||
private final CallerInfoLookupHelper.OnQueryCompleteListener mCallerInfoQueryListener =
|
||||
|
@ -646,6 +654,13 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
|
|||
*/
|
||||
private String mCallScreeningComponentName;
|
||||
|
||||
/**
|
||||
* When {@code true} indicates this call originated from a SIM-based {@link PhoneAccount}.
|
||||
* A sim-based {@link PhoneAccount} is one with {@link PhoneAccount#CAPABILITY_SIM_SUBSCRIPTION}
|
||||
* set.
|
||||
*/
|
||||
private boolean mIsSimCall;
|
||||
|
||||
/**
|
||||
* Persists the specified parameters and initializes the new instance.
|
||||
* @param context The context.
|
||||
|
@ -1077,6 +1092,10 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
|
|||
}
|
||||
}
|
||||
|
||||
public void handleOverrideDisconnectMessage(@Nullable CharSequence message) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the call state. Although there exists the notion of appropriate state transitions
|
||||
* (see {@link CallState}), in practice those expectations break down when cellular systems
|
||||
|
@ -1703,6 +1722,7 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
|
|||
PhoneAccountRegistrar phoneAccountRegistrar = mCallsManager.getPhoneAccountRegistrar();
|
||||
boolean isWorkCall = false;
|
||||
boolean isCallRecordingToneSupported = false;
|
||||
boolean isSimCall = false;
|
||||
PhoneAccount phoneAccount =
|
||||
phoneAccountRegistrar.getPhoneAccountUnchecked(mTargetPhoneAccountHandle);
|
||||
if (phoneAccount != null) {
|
||||
|
@ -1720,9 +1740,11 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
|
|||
PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION) && phoneAccount.getExtras() != null
|
||||
&& phoneAccount.getExtras().getBoolean(
|
||||
PhoneAccount.EXTRA_PLAY_CALL_RECORDING_TONE, false));
|
||||
isSimCall = phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION);
|
||||
}
|
||||
mIsWorkCall = isWorkCall;
|
||||
mUseCallRecordingTone = isCallRecordingToneSupported;
|
||||
mIsSimCall = isSimCall;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2952,6 +2974,14 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
|
|||
}
|
||||
requestHandover(phoneAccountHandle, videoState, handoverExtrasBundle, true);
|
||||
} else {
|
||||
// Relay bluetooth call quality reports to the call diagnostic service.
|
||||
if (BluetoothCallQualityReport.EVENT_BLUETOOTH_CALL_QUALITY_REPORT.equals(event)
|
||||
&& extras.containsKey(
|
||||
BluetoothCallQualityReport.EXTRA_BLUETOOTH_CALL_QUALITY_REPORT)) {
|
||||
notifyBluetoothCallQualityReport(extras.getParcelable(
|
||||
BluetoothCallQualityReport.EXTRA_BLUETOOTH_CALL_QUALITY_REPORT
|
||||
));
|
||||
}
|
||||
Log.addEvent(this, LogUtils.Events.CALL_EVENT, event);
|
||||
mConnectionService.sendCallEvent(this, event, extras);
|
||||
}
|
||||
|
@ -2961,6 +2991,17 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies listeners when a bluetooth quality report is received.
|
||||
* @param report The bluetooth quality report.
|
||||
*/
|
||||
void notifyBluetoothCallQualityReport(@NonNull BluetoothCallQualityReport report) {
|
||||
Log.addEvent(this, LogUtils.Events.BT_QUALITY_REPORT, "choppy=" + report.isChoppyVoice());
|
||||
for (Listener l : mListeners) {
|
||||
l.onBluetoothCallQualityReport(this, report);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates a handover of this Call to the {@link ConnectionService} identified
|
||||
* by destAcct.
|
||||
|
@ -3691,6 +3732,17 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
|
|||
for (Listener l : mListeners) {
|
||||
l.onCallSwitchFailed(this);
|
||||
}
|
||||
} else if (Connection.EVENT_DEVICE_TO_DEVICE_MESSAGE.equals(event)
|
||||
&& extras != null && extras.containsKey(
|
||||
Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_TYPE)
|
||||
&& extras.containsKey(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_VALUE)) {
|
||||
// Relay an incoming D2D message to interested listeners; most notably the
|
||||
// CallDiagnosticService.
|
||||
int messageType = extras.getInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_TYPE);
|
||||
int messageValue = extras.getInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_VALUE);
|
||||
for (Listener l : mListeners) {
|
||||
l.onReceivedDeviceToDeviceMessage(this, messageType, messageValue);
|
||||
}
|
||||
} else {
|
||||
for (Listener l : mListeners) {
|
||||
l.onConnectionEvent(this, event, extras);
|
||||
|
@ -3891,6 +3943,44 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a device to device message to the other part of the call.
|
||||
* @param message the message type to send.
|
||||
* @param value the value for the message.
|
||||
*/
|
||||
public void sendDeviceToDeviceMessage(@DiagnosticCall.MessageType int message, int value) {
|
||||
Log.i(this, "sendDeviceToDeviceMessage; callId=%s, msg=%d/%d", getId(), message, value);
|
||||
Bundle extras = new Bundle();
|
||||
extras.putInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_TYPE, message);
|
||||
extras.putInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_VALUE, value);
|
||||
// Send to the connection service.
|
||||
sendCallEvent(Connection.EVENT_DEVICE_TO_DEVICE_MESSAGE, extras);
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals to the Dialer app to start displaying a diagnostic message.
|
||||
* @param messageId a unique ID for the message to display.
|
||||
* @param message the message to display.
|
||||
*/
|
||||
public void displayDiagnosticMessage(int messageId, @NonNull CharSequence message) {
|
||||
Bundle extras = new Bundle();
|
||||
extras.putInt(android.telecom.Call.EXTRA_DIAGNOSTIC_MESSAGE_ID, messageId);
|
||||
extras.putCharSequence(android.telecom.Call.EXTRA_DIAGNOSTIC_MESSAGE, message);
|
||||
// Send to the dialer.
|
||||
onConnectionEvent(android.telecom.Call.EVENT_DISPLAY_DIAGNOSTIC_MESSAGE, extras);
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals to the Dialer app to stop displaying a diagnostic message.
|
||||
* @param messageId a unique ID for the message to clear.
|
||||
*/
|
||||
public void clearDiagnosticMessage(int messageId) {
|
||||
Bundle extras = new Bundle();
|
||||
extras.putInt(android.telecom.Call.EXTRA_DIAGNOSTIC_MESSAGE_ID, messageId);
|
||||
// Send to the dialer.
|
||||
onConnectionEvent(android.telecom.Call.EVENT_CLEAR_DIAGNOSTIC_MESSAGE, extras);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remaps the call direction as indicated by an {@link android.telecom.Call.Details} direction
|
||||
* constant to the constants (e.g. {@link #CALL_DIRECTION_INCOMING}) used in this call class.
|
||||
|
@ -3977,4 +4067,13 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code true} when this call originated from a SIM-based {@link PhoneAccount}.
|
||||
* A sim-based {@link PhoneAccount} is one with {@link PhoneAccount#CAPABILITY_SIM_SUBSCRIPTION}
|
||||
* set.
|
||||
*/
|
||||
public boolean isSimCall() {
|
||||
return mIsSimCall;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* 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 com.android.server.telecom;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.os.Binder;
|
||||
import android.os.RemoteException;
|
||||
import android.telecom.CallDiagnosticService;
|
||||
import android.telecom.DiagnosticCall;
|
||||
import android.telecom.Log;
|
||||
|
||||
import com.android.internal.telecom.ICallDiagnosticServiceAdapter;
|
||||
import com.android.internal.telecom.IInCallAdapter;
|
||||
|
||||
/**
|
||||
* Adapter class used to provide a path for messages FROM a {@link CallDiagnosticService} back to
|
||||
* the telecom stack.
|
||||
*/
|
||||
public class CallDiagnosticServiceAdapter extends ICallDiagnosticServiceAdapter.Stub {
|
||||
public interface TelecomAdapter {
|
||||
void displayDiagnosticMessage(String callId, int messageId, CharSequence message);
|
||||
void clearDiagnosticMessage(String callId, int messageId);
|
||||
void sendDeviceToDeviceMessage(String callId, @DiagnosticCall.MessageType int message,
|
||||
int value);
|
||||
void overrideDisconnectMessage(String callId, CharSequence message);
|
||||
}
|
||||
|
||||
private final TelecomAdapter mTelecomAdapter;
|
||||
private final String mOwnerPackageName;
|
||||
private final String mOwnerPackageAbbreviation;
|
||||
private final TelecomSystem.SyncRoot mLock;
|
||||
|
||||
CallDiagnosticServiceAdapter(@NonNull TelecomAdapter telecomAdapter,
|
||||
@NonNull String ownerPackageName, @NonNull TelecomSystem.SyncRoot lock) {
|
||||
mTelecomAdapter = telecomAdapter;
|
||||
mOwnerPackageName = ownerPackageName;
|
||||
mOwnerPackageAbbreviation = Log.getPackageAbbreviation(ownerPackageName);
|
||||
mLock = lock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayDiagnosticMessage(String callId, int messageId, CharSequence message)
|
||||
throws RemoteException {
|
||||
try {
|
||||
Log.startSession("CDSA.dDM", mOwnerPackageAbbreviation);
|
||||
long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
Log.i(this, "displayDiagnosticMessage; callId=%s, msg=%d/%s", callId, messageId,
|
||||
message);
|
||||
mTelecomAdapter.displayDiagnosticMessage(callId, messageId, message);
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
} finally {
|
||||
Log.endSession();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearDiagnosticMessage(String callId, int messageId) throws RemoteException {
|
||||
try {
|
||||
Log.startSession("CDSA.cDM", mOwnerPackageAbbreviation);
|
||||
long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
Log.i(this, "clearDiagnosticMessage; callId=%s, msg=%d", callId, messageId);
|
||||
mTelecomAdapter.clearDiagnosticMessage(callId, messageId);
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
} finally {
|
||||
Log.endSession();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendDeviceToDeviceMessage(String callId, @DiagnosticCall.MessageType int message,
|
||||
int value)
|
||||
throws RemoteException {
|
||||
try {
|
||||
Log.startSession("CDSA.sDTDM", mOwnerPackageAbbreviation);
|
||||
long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
Log.i(this, "sendDeviceToDeviceMessage; callId=%s, msg=%d/%d", callId, message,
|
||||
value);
|
||||
mTelecomAdapter.sendDeviceToDeviceMessage(callId, message, value);
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
} finally {
|
||||
Log.endSession();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void overrideDisconnectMessage(String callId, CharSequence message)
|
||||
throws RemoteException {
|
||||
try {
|
||||
Log.startSession("CDSA.oDM", mOwnerPackageAbbreviation);
|
||||
long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
Log.i(this, "overrideDisconnectMessage; callId=%s, msg=%s", callId, message);
|
||||
mTelecomAdapter.overrideDisconnectMessage(callId, message);
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
} finally {
|
||||
Log.endSession();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,654 @@
|
|||
/*
|
||||
* 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 com.android.server.telecom;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.RequiresPermission;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.telecom.BluetoothCallQualityReport;
|
||||
import android.telecom.CallAudioState;
|
||||
import android.telecom.CallDiagnosticService;
|
||||
import android.telecom.ConnectionService;
|
||||
import android.telecom.DiagnosticCall;
|
||||
import android.telecom.InCallService;
|
||||
import android.telecom.Log;
|
||||
import android.telecom.ParcelableCall;
|
||||
import android.telephony.ims.ImsReasonInfo;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.android.internal.telecom.ICallDiagnosticService;
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Responsible for maintaining binding to the {@link CallDiagnosticService} defined by the
|
||||
* {@code call_diagnostic_service_package_name} key in the
|
||||
* {@code packages/services/Telecomm/res/values/config.xml} file.
|
||||
*/
|
||||
public class CallDiagnosticServiceController extends CallsManagerListenerBase {
|
||||
/**
|
||||
* Context dependencies for the {@link CallDiagnosticServiceController}.
|
||||
*/
|
||||
public interface ContextProxy {
|
||||
List<ResolveInfo> queryIntentServicesAsUser(@NonNull Intent intent,
|
||||
@PackageManager.ResolveInfoFlags int flags, @UserIdInt int userId);
|
||||
boolean bindServiceAsUser(@NonNull @RequiresPermission Intent service,
|
||||
@NonNull ServiceConnection conn, int flags, @NonNull UserHandle user);
|
||||
void unbindService(@NonNull ServiceConnection conn);
|
||||
UserHandle getCurrentUserHandle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener for {@link Call} events; used to propagate these changes to the
|
||||
* {@link CallDiagnosticService}.
|
||||
*/
|
||||
private final Call.Listener mCallListener = new Call.ListenerBase() {
|
||||
@Override
|
||||
public void onConnectionCapabilitiesChanged(Call call) {
|
||||
updateCall(call);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionPropertiesChanged(Call call, boolean didRttChange) {
|
||||
updateCall(call);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens for changes to extras reported by a Telecom {@link Call}.
|
||||
*
|
||||
* Extras changes can originate from a {@link ConnectionService} or an {@link InCallService}
|
||||
* so we will only trigger an update of the call information if the source of the extras
|
||||
* change was a {@link ConnectionService}.
|
||||
*
|
||||
* @param call The call.
|
||||
* @param source The source of the extras change ({@link Call#SOURCE_CONNECTION_SERVICE} or
|
||||
* {@link Call#SOURCE_INCALL_SERVICE}).
|
||||
* @param extras The extras.
|
||||
*/
|
||||
@Override
|
||||
public void onExtrasChanged(Call call, int source, Bundle extras) {
|
||||
// Do not inform InCallServices of changes which originated there.
|
||||
if (source == Call.SOURCE_INCALL_SERVICE) {
|
||||
return;
|
||||
}
|
||||
updateCall(call);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens for changes to extras reported by a Telecom {@link Call}.
|
||||
*
|
||||
* Extras changes can originate from a {@link ConnectionService} or an {@link InCallService}
|
||||
* so we will only trigger an update of the call information if the source of the extras
|
||||
* change was a {@link ConnectionService}.
|
||||
* @param call The call.
|
||||
* @param source The source of the extras change ({@link Call#SOURCE_CONNECTION_SERVICE} or
|
||||
* {@link Call#SOURCE_INCALL_SERVICE}).
|
||||
* @param keys The extra key removed
|
||||
*/
|
||||
@Override
|
||||
public void onExtrasRemoved(Call call, int source, List<String> keys) {
|
||||
// Do not inform InCallServices of changes which originated there.
|
||||
if (source == Call.SOURCE_INCALL_SERVICE) {
|
||||
return;
|
||||
}
|
||||
updateCall(call);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles changes to the video state of a call.
|
||||
* @param call
|
||||
* @param previousVideoState
|
||||
* @param newVideoState
|
||||
*/
|
||||
@Override
|
||||
public void onVideoStateChanged(Call call, int previousVideoState, int newVideoState) {
|
||||
updateCall(call);
|
||||
}
|
||||
|
||||
/**
|
||||
* Relays a bluetooth call quality report received from the Bluetooth stack to the
|
||||
* CallDiagnosticService.
|
||||
* @param call The call.
|
||||
* @param report The received report.
|
||||
*/
|
||||
@Override
|
||||
public void onBluetoothCallQualityReport(Call call, BluetoothCallQualityReport report) {
|
||||
handleBluetoothCallQualityReport(call, report);
|
||||
}
|
||||
|
||||
/**
|
||||
* Relays a device to device message received from Telephony to the CallDiagnosticService.
|
||||
* @param call
|
||||
* @param messageType
|
||||
* @param messageValue
|
||||
*/
|
||||
@Override
|
||||
public void onReceivedDeviceToDeviceMessage(Call call, int messageType, int messageValue) {
|
||||
handleReceivedDeviceToDeviceMessage(call, messageType, messageValue);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* {@link ServiceConnection} handling changes to binding of the {@link CallDiagnosticService}.
|
||||
*/
|
||||
private class CallDiagnosticServiceConnection implements ServiceConnection {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
Log.startSession("CDSC.oSC", Log.getPackageAbbreviation(name));
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
mCallDiagnosticService = ICallDiagnosticService.Stub.asInterface(service);
|
||||
|
||||
handleConnectionComplete(mCallDiagnosticService);
|
||||
}
|
||||
Log.i(CallDiagnosticServiceController.this, "onServiceConnected: cmp=%s", name);
|
||||
} finally {
|
||||
Log.endSession();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
Log.startSession("CDSC.oSD", Log.getPackageAbbreviation(name));
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
mCallDiagnosticService = null;
|
||||
mConnection = null;
|
||||
}
|
||||
Log.i(CallDiagnosticServiceController.this, "onServiceDisconnected: cmp=%s", name);
|
||||
} finally {
|
||||
Log.endSession();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindingDied(ComponentName name) {
|
||||
Log.startSession("CDSC.oBD", Log.getPackageAbbreviation(name));
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
mCallDiagnosticService = null;
|
||||
mConnection = null;
|
||||
}
|
||||
Log.w(CallDiagnosticServiceController.this, "onBindingDied: cmp=%s", name);
|
||||
} finally {
|
||||
Log.endSession();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNullBinding(ComponentName name) {
|
||||
Log.startSession("CDSC.oNB", Log.getPackageAbbreviation(name));
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
maybeUnbindCallScreeningService();
|
||||
}
|
||||
} finally {
|
||||
Log.endSession();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final String mPackageName;
|
||||
private final ContextProxy mContextProxy;
|
||||
private String mTestPackageName;
|
||||
private CallDiagnosticServiceConnection mConnection;
|
||||
private CallDiagnosticServiceAdapter mAdapter;
|
||||
private final TelecomSystem.SyncRoot mLock;
|
||||
private ICallDiagnosticService mCallDiagnosticService;
|
||||
private final CallIdMapper mCallIdMapper = new CallIdMapper(Call::getId);
|
||||
|
||||
public CallDiagnosticServiceController(@NonNull ContextProxy contextProxy,
|
||||
@Nullable String packageName, @NonNull TelecomSystem.SyncRoot lock) {
|
||||
mContextProxy = contextProxy;
|
||||
mPackageName = packageName;
|
||||
mLock = lock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles Telecom adding new calls. Will bind to the call diagnostic service if needed and
|
||||
* send the calls, or send to an already bound service.
|
||||
* @param call The call to add.
|
||||
*/
|
||||
@Override
|
||||
public void onCallAdded(@NonNull Call call) {
|
||||
if (!call.isSimCall() || call.isExternalCall()) {
|
||||
Log.i(this, "onCallAdded: skipping call %s as non-sim or external.", call.getId());
|
||||
return;
|
||||
}
|
||||
if (mCallIdMapper.getCallId(call) == null) {
|
||||
mCallIdMapper.addCall(call);
|
||||
call.addListener(mCallListener);
|
||||
}
|
||||
if (isConnected()) {
|
||||
sendCallToBoundService(call, mCallDiagnosticService);
|
||||
} else {
|
||||
maybeBindCallDiagnosticService();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles Telecom removal of calls; will remove the call from the bound service and if the
|
||||
* number of tracked calls falls to zero, unbind from the service.
|
||||
* @param call The call to remove from the bound CDS.
|
||||
*/
|
||||
@Override
|
||||
public void onCallRemoved(@NonNull Call call) {
|
||||
if (!call.isSimCall() || call.isExternalCall()) {
|
||||
Log.i(this, "onCallRemoved: skipping call %s as non-sim or external.", call.getId());
|
||||
return;
|
||||
}
|
||||
mCallIdMapper.removeCall(call);
|
||||
call.removeListener(mCallListener);
|
||||
removeCallFromBoundService(call, mCallDiagnosticService);
|
||||
|
||||
if (mCallIdMapper.getCalls().size() == 0) {
|
||||
maybeUnbindCallScreeningService();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCallStateChanged(Call call, int oldState, int newState) {
|
||||
updateCall(call);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCallAudioStateChanged(CallAudioState oldCallAudioState,
|
||||
CallAudioState newCallAudioState) {
|
||||
if (mCallDiagnosticService != null) {
|
||||
try {
|
||||
mCallDiagnosticService.updateCallAudioState(newCallAudioState);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(this, "onCallAudioStateChanged: failed %s", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the test call diagnostic service; used by the telecom command line command to override
|
||||
* the {@link CallDiagnosticService} to bind to for CTS test purposes.
|
||||
* @param packageName The package name to set to.
|
||||
*/
|
||||
public void setTestCallDiagnosticService(@Nullable String packageName) {
|
||||
if (TextUtils.isEmpty(packageName)) {
|
||||
mTestPackageName = null;
|
||||
} else {
|
||||
mTestPackageName = packageName;
|
||||
}
|
||||
|
||||
Log.i(this, "setTestCallDiagnosticService: packageName=%s", packageName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the active call diagnostic service, taking into account the test override.
|
||||
* @return The package name of the active call diagnostic service.
|
||||
*/
|
||||
private @Nullable String getActiveCallDiagnosticService() {
|
||||
if (mTestPackageName != null) {
|
||||
return mTestPackageName;
|
||||
}
|
||||
|
||||
return mPackageName;
|
||||
}
|
||||
|
||||
/**
|
||||
* If we are not already bound to the {@link CallDiagnosticService}, attempts to initiate a
|
||||
* binding tho that service.
|
||||
* @return {@code true} if we bound, {@code false} otherwise.
|
||||
*/
|
||||
private boolean maybeBindCallDiagnosticService() {
|
||||
if (mConnection != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mConnection = new CallDiagnosticServiceConnection();
|
||||
boolean bound = bindCallDiagnosticService(mContextProxy.getCurrentUserHandle(),
|
||||
getActiveCallDiagnosticService(), mConnection);
|
||||
if (!bound) {
|
||||
mConnection = null;
|
||||
}
|
||||
return bound;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs binding to the {@link CallDiagnosticService}.
|
||||
* @param userHandle user name to bind via.
|
||||
* @param packageName package name of the CDS.
|
||||
* @param serviceConnection The service connection to be notified of bind events.
|
||||
* @return
|
||||
*/
|
||||
private boolean bindCallDiagnosticService(UserHandle userHandle,
|
||||
String packageName, CallDiagnosticServiceConnection serviceConnection) {
|
||||
|
||||
if (TextUtils.isEmpty(packageName)) {
|
||||
Log.i(this, "bindCallDiagnosticService: no package; skip binding.");
|
||||
return false;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(CallDiagnosticService.SERVICE_INTERFACE)
|
||||
.setPackage(packageName);
|
||||
Log.i(this, "bindCallDiagnosticService: user %d.", userHandle.getIdentifier());
|
||||
List<ResolveInfo> entries = mContextProxy.queryIntentServicesAsUser(intent, 0,
|
||||
userHandle.getIdentifier());
|
||||
if (entries.isEmpty()) {
|
||||
Log.i(this, "bindCallDiagnosticService: %s has no service.", packageName);
|
||||
return false;
|
||||
}
|
||||
|
||||
ResolveInfo entry = entries.get(0);
|
||||
if (entry.serviceInfo == null) {
|
||||
Log.i(this, "bindCallDiagnosticService: %s has no service info.", packageName);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entry.serviceInfo.permission == null || !entry.serviceInfo.permission.equals(
|
||||
Manifest.permission.BIND_CALL_DIAGNOSTIC_SERVICE)) {
|
||||
Log.i(this, "bindCallDiagnosticService: %s doesn't require "
|
||||
+ "BIND_CALL_DIAGNOSTIC_SERVICE.", packageName);
|
||||
return false;
|
||||
}
|
||||
|
||||
ComponentName componentName =
|
||||
new ComponentName(entry.serviceInfo.packageName, entry.serviceInfo.name);
|
||||
intent.setComponent(componentName);
|
||||
if (mContextProxy.bindServiceAsUser(
|
||||
intent,
|
||||
serviceConnection,
|
||||
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
|
||||
UserHandle.CURRENT)) {
|
||||
Log.d(this, "bindCallDiagnosticService, found service, waiting for it to connect");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* If we are bound to a {@link CallDiagnosticService}, unbind from it.
|
||||
*/
|
||||
public void maybeUnbindCallScreeningService() {
|
||||
if (mConnection != null) {
|
||||
Log.i(this, "maybeUnbindCallScreeningService - unbinding from %s",
|
||||
getActiveCallDiagnosticService());
|
||||
try {
|
||||
mContextProxy.unbindService(mConnection);
|
||||
mCallDiagnosticService = null;
|
||||
mConnection = null;
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.i(this, "maybeUnbindCallScreeningService: Exception when unbind %s : %s",
|
||||
getActiveCallDiagnosticService(), e.getMessage());
|
||||
}
|
||||
} else {
|
||||
Log.w(this, "maybeUnbindCallScreeningService - already unbound");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the abstracted Telecom functionality the {@link CallDiagnosticServiceAdapter}
|
||||
* depends on.
|
||||
*/
|
||||
private CallDiagnosticServiceAdapter.TelecomAdapter mTelecomAdapter =
|
||||
new CallDiagnosticServiceAdapter.TelecomAdapter() {
|
||||
|
||||
@Override
|
||||
public void displayDiagnosticMessage(String callId, int messageId, CharSequence message) {
|
||||
handleDisplayDiagnosticMessage(callId, messageId, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearDiagnosticMessage(String callId, int messageId) {
|
||||
handleClearDiagnosticMessage(callId, messageId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendDeviceToDeviceMessage(String callId,
|
||||
@DiagnosticCall.MessageType int message, int value) {
|
||||
handleSendD2DMessage(callId, message, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void overrideDisconnectMessage(String callId, CharSequence message) {
|
||||
handleOverrideDisconnectMessage(callId, message);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends all calls to the specified {@link CallDiagnosticService}.
|
||||
* @param callDiagnosticService the CDS to send calls to.
|
||||
*/
|
||||
private void handleConnectionComplete(@NonNull ICallDiagnosticService callDiagnosticService) {
|
||||
mAdapter = new CallDiagnosticServiceAdapter(mTelecomAdapter,
|
||||
getActiveCallDiagnosticService(), mLock);
|
||||
try {
|
||||
// Add adapter for communication back from the call diagnostic service to Telecom.
|
||||
callDiagnosticService.setAdapter(mAdapter);
|
||||
|
||||
// Loop through all the calls we've got ready to send since binding.
|
||||
for (Call call : mCallIdMapper.getCalls()) {
|
||||
sendCallToBoundService(call, callDiagnosticService);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.w(this, "handleConnectionComplete: error=%s", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a request from a {@link CallDiagnosticService} to display a diagnostic message.
|
||||
* @param callId the ID of the call to display the message for.
|
||||
* @param message the message.
|
||||
*/
|
||||
private void handleDisplayDiagnosticMessage(@NonNull String callId, int messageId,
|
||||
@Nullable CharSequence message) {
|
||||
Call call = mCallIdMapper.getCall(callId);
|
||||
if (call == null) {
|
||||
Log.w(this, "handleDisplayDiagnosticMessage: callId=%s; msg=%d/%s; invalid call",
|
||||
callId, messageId, message);
|
||||
return;
|
||||
}
|
||||
Log.i(this, "handleDisplayDiagnosticMessage: callId=%s; msg=%d/%s; invalid call",
|
||||
callId, messageId, message);
|
||||
call.displayDiagnosticMessage(messageId, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a request from a {@link CallDiagnosticService} to clear a previously displayed
|
||||
* diagnostic message.
|
||||
* @param callId the ID of the call to display the message for.
|
||||
* @param messageId the message ID which was previous posted.
|
||||
*/
|
||||
private void handleClearDiagnosticMessage(@NonNull String callId, int messageId) {
|
||||
Call call = mCallIdMapper.getCall(callId);
|
||||
if (call == null) {
|
||||
Log.w(this, "handleClearDiagnosticMessage: callId=%s; msg=%d; invalid call",
|
||||
callId, messageId);
|
||||
return;
|
||||
}
|
||||
Log.i(this, "handleClearDiagnosticMessage: callId=%s; msg=%d; invalid call",
|
||||
callId, messageId);
|
||||
call.clearDiagnosticMessage(messageId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a request from a {@link CallDiagnosticService} to send a device to device message.
|
||||
* @param callId The ID of the call to send the D2D message for.
|
||||
* @param message The message type.
|
||||
* @param value The message value.
|
||||
*/
|
||||
private void handleSendD2DMessage(@NonNull String callId,
|
||||
@DiagnosticCall.MessageType int message, int value) {
|
||||
Call call = mCallIdMapper.getCall(callId);
|
||||
if (call == null) {
|
||||
Log.w(this, "handleSendD2DMessage: callId=%s; msg=%d/%d; invalid call", callId,
|
||||
message, value);
|
||||
return;
|
||||
}
|
||||
Log.i(this, "handleSendD2DMessage: callId=%s; msg=%d/%d", callId, message, value);
|
||||
call.sendDeviceToDeviceMessage(message, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a request from a {@link CallDiagnosticService} to override the disconnect message
|
||||
* for a call. This is the response path from a previous call into the
|
||||
* {@link CallDiagnosticService} via {@link DiagnosticCall#onCallDisconnected(ImsReasonInfo)}.
|
||||
* @param callId The telecom call ID the disconnect override is pending for.
|
||||
* @param message The new disconnect message, or {@code null} if no override.
|
||||
*/
|
||||
private void handleOverrideDisconnectMessage(@NonNull String callId,
|
||||
@Nullable CharSequence message) {
|
||||
Call call = mCallIdMapper.getCall(callId);
|
||||
if (call == null) {
|
||||
Log.w(this, "handleOverrideDisconnectMessage: callId=%s; msg=%s; invalid call", callId,
|
||||
message);
|
||||
return;
|
||||
}
|
||||
Log.i(this, "handleOverrideDisconnectMessage: callId=%s; msg=%s", callId, message);
|
||||
call.handleOverrideDisconnectMessage(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a single call to the bound {@link CallDiagnosticService}.
|
||||
* @param call The call to send.
|
||||
* @param callDiagnosticService The CDS to send it to.
|
||||
*/
|
||||
private void sendCallToBoundService(@NonNull Call call,
|
||||
@NonNull ICallDiagnosticService callDiagnosticService) {
|
||||
try {
|
||||
if (isConnected()) {
|
||||
Log.w(this, "sendCallToBoundService: initializing %s", call.getId());
|
||||
callDiagnosticService.initializeDiagnosticCall(getParceledCall(call));
|
||||
} else {
|
||||
Log.w(this, "sendCallToBoundService: not bound, skipping %s", call.getId());
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.w(this, "sendCallToBoundService: callId=%s, exception=%s", call.getId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a call from a bound {@link CallDiagnosticService}.
|
||||
* @param call The call to remove.
|
||||
* @param callDiagnosticService The CDS to remove it from.
|
||||
*/
|
||||
private void removeCallFromBoundService(@NonNull Call call,
|
||||
@NonNull ICallDiagnosticService callDiagnosticService) {
|
||||
try {
|
||||
if (isConnected()) {
|
||||
callDiagnosticService.removeDiagnosticCall(call.getId());
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.w(this, "removeCallFromBoundService: callId=%s, exception=%s", call.getId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code true} if the call diagnostic service is bound/connected.
|
||||
*/
|
||||
private boolean isConnected() {
|
||||
return mCallDiagnosticService != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the Call diagnostic service with changes to a call.
|
||||
* @param call The updated call.
|
||||
*/
|
||||
private void updateCall(@NonNull Call call) {
|
||||
try {
|
||||
if (isConnected()) {
|
||||
mCallDiagnosticService.updateCall(getParceledCall(call));
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.w(this, "updateCall: callId=%s, exception=%s", call.getId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the call diagnostic service with a received bluetooth quality report.
|
||||
* @param call The call.
|
||||
* @param report The bluetooth call quality report.
|
||||
*/
|
||||
private void handleBluetoothCallQualityReport(@NonNull Call call,
|
||||
@NonNull BluetoothCallQualityReport report) {
|
||||
try {
|
||||
if (isConnected()) {
|
||||
mCallDiagnosticService.receiveBluetoothCallQualityReport(report);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.w(this, "handleBluetoothCallQualityReport: callId=%s, exception=%s", call.getId(),
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Informs a CallDiagnosticService of an incoming device to device message which was received
|
||||
* via the carrier network.
|
||||
* @param call the call the message was received via.
|
||||
* @param messageType The message type.
|
||||
* @param messageValue The message value.
|
||||
*/
|
||||
private void handleReceivedDeviceToDeviceMessage(@NonNull Call call, int messageType,
|
||||
int messageValue) {
|
||||
try {
|
||||
if (isConnected()) {
|
||||
mCallDiagnosticService.receiveDeviceToDeviceMessage(call.getId(), messageType,
|
||||
messageValue);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.w(this, "handleReceivedDeviceToDeviceMessage: callId=%s, exception=%s",
|
||||
call.getId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a parcelled representation of a call for transport to the service.
|
||||
* @param call The call.
|
||||
* @return The parcelled call.
|
||||
*/
|
||||
private @NonNull ParcelableCall getParceledCall(@NonNull Call call) {
|
||||
return ParcelableCallUtils.toParcelableCall(
|
||||
call,
|
||||
false /* includeVideoProvider */,
|
||||
null /* phoneAcctRegistrar */,
|
||||
false /* supportsExternalCalls */,
|
||||
false /* includeRttCall */,
|
||||
false /* isForSystemDialer */
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps the state of the {@link CallDiagnosticServiceController}.
|
||||
*
|
||||
* @param pw The {@code IndentingPrintWriter} to write the state to.
|
||||
*/
|
||||
public void dump(IndentingPrintWriter pw) {
|
||||
pw.print("activeCallDiagnosticService: ");
|
||||
pw.println(getActiveCallDiagnosticService());
|
||||
pw.print("isConnected: ");
|
||||
pw.println(isConnected());
|
||||
}
|
||||
}
|
|
@ -332,6 +332,7 @@ public class CallsManager extends Call.ListenerBase
|
|||
private final ConnectionServiceRepository mConnectionServiceRepository;
|
||||
private final DtmfLocalTonePlayer mDtmfLocalTonePlayer;
|
||||
private final InCallController mInCallController;
|
||||
private final CallDiagnosticServiceController mCallDiagnosticServiceController;
|
||||
private final CallAudioManager mCallAudioManager;
|
||||
private final CallRecordingTonePlayer mCallRecordingTonePlayer;
|
||||
private RespondViaSmsManager mRespondViaSmsManager;
|
||||
|
@ -487,6 +488,7 @@ public class CallsManager extends Call.ListenerBase
|
|||
CallAudioRouteStateMachine.Factory callAudioRouteStateMachineFactory,
|
||||
CallAudioModeStateMachine.Factory callAudioModeStateMachineFactory,
|
||||
InCallControllerFactory inCallControllerFactory,
|
||||
CallDiagnosticServiceController callDiagnosticServiceController,
|
||||
RoleManagerAdapter roleManagerAdapter,
|
||||
ToastFactory toastFactory) {
|
||||
mContext = context;
|
||||
|
@ -543,6 +545,7 @@ public class CallsManager extends Call.ListenerBase
|
|||
mInCallController = inCallControllerFactory.create(context, mLock, this,
|
||||
systemStateHelper, defaultDialerCache, mTimeoutsAdapter,
|
||||
emergencyCallHelper);
|
||||
mCallDiagnosticServiceController = callDiagnosticServiceController;
|
||||
mRinger = new Ringer(playerFactory, context, systemSettingsUtil, asyncRingtonePlayer,
|
||||
ringtoneFactory, systemVibrator,
|
||||
new Ringer.VibrationEffectProxy(), mInCallController);
|
||||
|
@ -572,6 +575,7 @@ public class CallsManager extends Call.ListenerBase
|
|||
mListeners.add(mCallLogManager);
|
||||
mListeners.add(mPhoneStateBroadcaster);
|
||||
mListeners.add(mInCallController);
|
||||
mListeners.add(mCallDiagnosticServiceController);
|
||||
mListeners.add(mCallAudioManager);
|
||||
mListeners.add(mCallRecordingTonePlayer);
|
||||
mListeners.add(missedCallNotifier);
|
||||
|
@ -622,6 +626,10 @@ public class CallsManager extends Call.ListenerBase
|
|||
return mRoleManagerAdapter;
|
||||
}
|
||||
|
||||
public CallDiagnosticServiceController getCallDiagnosticServiceController() {
|
||||
return mCallDiagnosticServiceController;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccessfulOutgoingCall(Call call, int callState) {
|
||||
Log.v(this, "onSuccessfulOutgoingCall, %s", call);
|
||||
|
@ -3631,22 +3639,8 @@ public class CallsManager extends Call.ListenerBase
|
|||
Trace.beginSection("onCallStateChanged");
|
||||
|
||||
maybeHandleHandover(call, newState);
|
||||
notifyCallStateChanged(call, oldState, newState);
|
||||
|
||||
// Only broadcast state change for calls that are being tracked.
|
||||
if (mCalls.contains(call)) {
|
||||
updateCanAddCall();
|
||||
updateHasActiveRttCall();
|
||||
for (CallsManagerListener listener : mListeners) {
|
||||
if (LogUtils.SYSTRACE_DEBUG) {
|
||||
Trace.beginSection(listener.getClass().toString() +
|
||||
" onCallStateChanged");
|
||||
}
|
||||
listener.onCallStateChanged(call, oldState, newState);
|
||||
if (LogUtils.SYSTRACE_DEBUG) {
|
||||
Trace.endSection();
|
||||
}
|
||||
}
|
||||
}
|
||||
Trace.endSection();
|
||||
} else {
|
||||
Log.i(this, "failed in setting the state to new state");
|
||||
|
@ -3654,6 +3648,24 @@ public class CallsManager extends Call.ListenerBase
|
|||
}
|
||||
}
|
||||
|
||||
private void notifyCallStateChanged(Call call, int oldState, int newState) {
|
||||
// Only broadcast state change for calls that are being tracked.
|
||||
if (mCalls.contains(call)) {
|
||||
updateCanAddCall();
|
||||
updateHasActiveRttCall();
|
||||
for (CallsManagerListener listener : mListeners) {
|
||||
if (LogUtils.SYSTRACE_DEBUG) {
|
||||
Trace.beginSection(listener.getClass().toString() +
|
||||
" onCallStateChanged");
|
||||
}
|
||||
listener.onCallStateChanged(call, oldState, newState);
|
||||
if (LogUtils.SYSTRACE_DEBUG) {
|
||||
Trace.endSection();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies call state transitions for a call which trigger handover events.
|
||||
* - If this call has a handover to it which just started and this call goes active, treat
|
||||
|
@ -4691,6 +4703,13 @@ public class CallsManager extends Call.ListenerBase
|
|||
pw.decreaseIndent();
|
||||
}
|
||||
|
||||
if (mCallDiagnosticServiceController != null) {
|
||||
pw.println("mCallDiagnosticServiceController:");
|
||||
pw.increaseIndent();
|
||||
mCallDiagnosticServiceController.dump(pw);
|
||||
pw.decreaseIndent();
|
||||
}
|
||||
|
||||
if (mDefaultDialerCache != null) {
|
||||
pw.println("mDefaultDialerCache:");
|
||||
pw.increaseIndent();
|
||||
|
|
|
@ -196,6 +196,7 @@ public class LogUtils {
|
|||
public static final String REDIRECTION_USER_CONFIRMATION = "REDIRECTION_USER_CONFIRMATION";
|
||||
public static final String REDIRECTION_USER_CONFIRMED = "REDIRECTION_USER_CONFIRMED";
|
||||
public static final String REDIRECTION_USER_CANCELLED = "REDIRECTION_USER_CANCELLED";
|
||||
public static final String BT_QUALITY_REPORT = "BT_QUALITY_REPORT";
|
||||
|
||||
public static class Timings {
|
||||
public static final String ACCEPT_TIMING = "accept";
|
||||
|
|
|
@ -1899,6 +1899,30 @@ public class TelecomServiceImpl {
|
|||
Log.endSession();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTestCallDiagnosticService(String packageName) {
|
||||
try {
|
||||
Log.startSession("TSI.sTCDS");
|
||||
enforceModifyPermission();
|
||||
enforceShellOnly(Binder.getCallingUid(), "setTestCallDiagnosticService is for "
|
||||
+ "shell use only.");
|
||||
synchronized (mLock) {
|
||||
long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
CallDiagnosticServiceController controller =
|
||||
mCallsManager.getCallDiagnosticServiceController();
|
||||
if (controller != null) {
|
||||
controller.setTestCallDiagnosticService(packageName);
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
Log.endSession();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -36,8 +36,10 @@ import android.content.BroadcastReceiver;
|
|||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
|
@ -45,8 +47,11 @@ import android.telecom.Log;
|
|||
import android.telecom.PhoneAccountHandle;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Top-level Application class for Telecom.
|
||||
|
@ -260,6 +265,39 @@ public class TelecomSystem {
|
|||
}
|
||||
};
|
||||
|
||||
CallDiagnosticServiceController callDiagnosticServiceController =
|
||||
new CallDiagnosticServiceController(
|
||||
new CallDiagnosticServiceController.ContextProxy() {
|
||||
@Override
|
||||
public List<ResolveInfo> queryIntentServicesAsUser(
|
||||
@NonNull Intent intent, int flags, int userId) {
|
||||
return mContext.getPackageManager().queryIntentServicesAsUser(
|
||||
intent, flags, userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean bindServiceAsUser(@NonNull Intent service,
|
||||
@NonNull ServiceConnection conn, int flags,
|
||||
@NonNull UserHandle user) {
|
||||
return mContext.bindServiceAsUser(service, conn, flags, user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindService(@NonNull ServiceConnection conn) {
|
||||
mContext.unbindService(conn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserHandle getCurrentUserHandle() {
|
||||
return mCallsManager.getCurrentUserHandle();
|
||||
}
|
||||
},
|
||||
mContext.getResources().getString(
|
||||
com.android.server.telecom.R.string
|
||||
.call_diagnostic_service_package_name),
|
||||
mLock
|
||||
);
|
||||
|
||||
AudioProcessingNotification audioProcessingNotification =
|
||||
new AudioProcessingNotification(mContext);
|
||||
|
||||
|
@ -303,6 +341,7 @@ public class TelecomSystem {
|
|||
callAudioRouteStateMachineFactory,
|
||||
callAudioModeStateMachineFactory,
|
||||
inCallControllerFactory,
|
||||
callDiagnosticServiceController,
|
||||
roleManagerAdapter,
|
||||
toastFactory);
|
||||
|
||||
|
|
|
@ -39,6 +39,13 @@
|
|||
<uses-library android:name="android.test.runner"/>
|
||||
|
||||
<!-- Miscellaneous telecom app-related test activities. -->
|
||||
<service android:name="com.android.server.telecom.testapps.TestCallDiagnosticService"
|
||||
android:permission="android.permission.BIND_CALL_DIAGNOSTIC_SERVICE"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.telecom.CallDiagnosticService"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service android:name="com.android.server.telecom.testapps.TestConnectionService"
|
||||
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* 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 com.android.server.telecom.testapps;
|
||||
|
||||
import android.telecom.BluetoothCallQualityReport;
|
||||
import android.telecom.Call;
|
||||
import android.telecom.CallAudioState;
|
||||
import android.telecom.CallDiagnosticService;
|
||||
import android.telecom.DiagnosticCall;
|
||||
import android.telecom.Log;
|
||||
import android.telephony.CallQuality;
|
||||
import android.telephony.ims.ImsReasonInfo;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class TestCallDiagnosticService extends CallDiagnosticService {
|
||||
|
||||
public static final class TestDiagnosticCall extends DiagnosticCall {
|
||||
public Call.Details details;
|
||||
|
||||
TestDiagnosticCall(Call.Details details) {
|
||||
this.details = details;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCallDetailsChanged(@NonNull Call.Details details) {
|
||||
Log.i(this, "onCallDetailsChanged; %s", details);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceiveDeviceToDeviceMessage(int message, int value) {
|
||||
Log.i(this, "onReceiveDeviceToDeviceMessage; %d/%d", message, value);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public CharSequence onCallDisconnected(int disconnectCause, int preciseDisconnectCause) {
|
||||
Log.i(this, "onCallDisconnected");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public CharSequence onCallDisconnected(@NonNull ImsReasonInfo disconnectReason) {
|
||||
Log.i(this, "onCallDisconnected");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCallQualityReceived(@NonNull CallQuality callQuality) {
|
||||
Log.i(this, "onCallQualityReceived %s", callQuality);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public DiagnosticCall onInitializeDiagnosticCall(@NonNull Call.Details call) {
|
||||
Log.i(this, "onInitiatlizeDiagnosticCall %s", call);
|
||||
return new TestDiagnosticCall(call);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoveDiagnosticCall(@NonNull DiagnosticCall call) {
|
||||
Log.i(this, "onRemoveDiagnosticCall %s", call);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCallAudioStateChanged(@NonNull CallAudioState audioState) {
|
||||
Log.i(this, "onCallAudioStateChanged %s", audioState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBluetoothCallQualityReportReceived(
|
||||
@NonNull BluetoothCallQualityReport qualityReport) {
|
||||
Log.i(this, "onBluetoothCallQualityReportReceived %s", qualityReport);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,216 @@
|
|||
/*
|
||||
* 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 com.android.server.telecom.tests;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.telecom.ParcelableCall;
|
||||
|
||||
import com.android.internal.telecom.ICallDiagnosticService;
|
||||
import com.android.server.telecom.Call;
|
||||
import com.android.server.telecom.CallDiagnosticServiceController;
|
||||
import com.android.server.telecom.TelecomSystem;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public class CallDiagnosticServiceControllerTest {
|
||||
private static final String TEST_CDS_PACKAGE = "com.test.stuff";
|
||||
private static final String TEST_PACKAGE = "com.android.telecom.calldiagnosticservice";
|
||||
private static final String TEST_CLASS =
|
||||
"com.android.telecom.calldiagnosticservice.TestService";
|
||||
private static final ComponentName TEST_COMPONENT = new ComponentName(TEST_PACKAGE, TEST_CLASS);
|
||||
private static final List<ResolveInfo> RESOLVE_INFOS = new ArrayList<>();
|
||||
private static final ResolveInfo TEST_RESOLVE_INFO = new ResolveInfo();
|
||||
static {
|
||||
TEST_RESOLVE_INFO.serviceInfo = new ServiceInfo();
|
||||
TEST_RESOLVE_INFO.serviceInfo.packageName = TEST_PACKAGE;
|
||||
TEST_RESOLVE_INFO.serviceInfo.name = TEST_CLASS;
|
||||
TEST_RESOLVE_INFO.serviceInfo.permission = Manifest.permission.BIND_CALL_DIAGNOSTIC_SERVICE;
|
||||
RESOLVE_INFOS.add(TEST_RESOLVE_INFO);
|
||||
}
|
||||
private static final String ID_1 = "1";
|
||||
private static final String ID_2 = "2";
|
||||
|
||||
@Mock
|
||||
private CallDiagnosticServiceController.ContextProxy mContextProxy;
|
||||
@Mock
|
||||
private Call mCall;
|
||||
@Mock
|
||||
private Call mCallTwo;
|
||||
@Mock
|
||||
private ICallDiagnosticService mICallDiagnosticService;
|
||||
private TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
|
||||
|
||||
private CallDiagnosticServiceController mCallDiagnosticService;
|
||||
private ServiceConnection mServiceConnection;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
when(mCall.getId()).thenReturn(ID_1);
|
||||
when(mCall.isSimCall()).thenReturn(true);
|
||||
when(mCall.isExternalCall()).thenReturn(false);
|
||||
|
||||
when(mCallTwo.getId()).thenReturn(ID_2);
|
||||
when(mCallTwo.isSimCall()).thenReturn(true);
|
||||
when(mCallTwo.isExternalCall()).thenReturn(false);
|
||||
mServiceConnection = null;
|
||||
|
||||
// Mock out the context and other junk that we depend on.
|
||||
when(mContextProxy.queryIntentServicesAsUser(any(Intent.class), anyInt(), anyInt()))
|
||||
.thenReturn(RESOLVE_INFOS);
|
||||
when(mContextProxy.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class),
|
||||
anyInt(), any(UserHandle.class))).thenReturn(true);
|
||||
when(mContextProxy.getCurrentUserHandle()).thenReturn(UserHandle.CURRENT);
|
||||
|
||||
mCallDiagnosticService = new CallDiagnosticServiceController(mContextProxy,
|
||||
TEST_PACKAGE, mLock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify no binding takes place for a non-sim call.
|
||||
*/
|
||||
@Test
|
||||
public void testNoBindOnNonSimCall() {
|
||||
Call mockCall = Mockito.mock(Call.class);
|
||||
when(mockCall.isSimCall()).thenReturn(false);
|
||||
|
||||
mCallDiagnosticService.onCallAdded(mockCall);
|
||||
|
||||
verify(mContextProxy, never()).bindServiceAsUser(any(Intent.class),
|
||||
any(ServiceConnection.class), anyInt(), any(UserHandle.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify no binding takes place for a SIM external call.
|
||||
*/
|
||||
@Test
|
||||
public void testNoBindOnExternalCall() {
|
||||
Call mockCall = Mockito.mock(Call.class);
|
||||
when(mockCall.isSimCall()).thenReturn(true);
|
||||
when(mockCall.isExternalCall()).thenReturn(true);
|
||||
|
||||
mCallDiagnosticService.onCallAdded(mockCall);
|
||||
|
||||
verify(mContextProxy, never()).bindServiceAsUser(any(Intent.class),
|
||||
any(ServiceConnection.class), anyInt(), any(UserHandle.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a valid SIM call causes binding to initiate.
|
||||
*/
|
||||
@Test
|
||||
public void testAddSimCallCausesBind() throws RemoteException {
|
||||
mCallDiagnosticService.onCallAdded(mCall);
|
||||
|
||||
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
|
||||
ArgumentCaptor<ServiceConnection> serviceConnectionCaptor = ArgumentCaptor.forClass(
|
||||
ServiceConnection.class);
|
||||
verify(mContextProxy).bindServiceAsUser(intentCaptor.capture(),
|
||||
serviceConnectionCaptor.capture(), anyInt(), any(UserHandle.class));
|
||||
assertEquals(TEST_PACKAGE, intentCaptor.getValue().getPackage());
|
||||
|
||||
// Now we'll pretend bind completed and we sent back the binder.
|
||||
IBinder mockBinder = mock(IBinder.class);
|
||||
when(mockBinder.queryLocalInterface(anyString())).thenReturn(mICallDiagnosticService);
|
||||
serviceConnectionCaptor.getValue().onServiceConnected(TEST_COMPONENT, mockBinder);
|
||||
mServiceConnection = serviceConnectionCaptor.getValue();
|
||||
|
||||
// Make sure it's sent
|
||||
verify(mICallDiagnosticService).initializeDiagnosticCall(any(ParcelableCall.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify removing the active call causes it to be removed from the CallDiagnosticService and
|
||||
* that an unbind takes place.
|
||||
*/
|
||||
@Test
|
||||
public void testRemoveSimCallCausesRemoveAndUnbind() throws RemoteException {
|
||||
testAddSimCallCausesBind();
|
||||
mCallDiagnosticService.onCallRemoved(mCall);
|
||||
|
||||
verify(mICallDiagnosticService).removeDiagnosticCall(eq(ID_1));
|
||||
verify(mContextProxy).unbindService(eq(mServiceConnection));
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to add and remove two and verify bind/unbind.
|
||||
*/
|
||||
@Test
|
||||
public void testAddTwo() throws RemoteException {
|
||||
testAddSimCallCausesBind();
|
||||
mCallDiagnosticService.onCallAdded(mCallTwo);
|
||||
verify(mICallDiagnosticService, times(2)).initializeDiagnosticCall(
|
||||
any(ParcelableCall.class));
|
||||
|
||||
mCallDiagnosticService.onCallRemoved(mCall);
|
||||
// Not yet!
|
||||
verify(mContextProxy, never()).unbindService(eq(mServiceConnection));
|
||||
|
||||
mCallDiagnosticService.onCallRemoved(mCallTwo);
|
||||
|
||||
verify(mICallDiagnosticService).removeDiagnosticCall(eq(ID_1));
|
||||
verify(mICallDiagnosticService).removeDiagnosticCall(eq(ID_2));
|
||||
verify(mContextProxy).unbindService(eq(mServiceConnection));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies we can override the call diagnostic service package to a test package (used by CTS
|
||||
* tests).
|
||||
*/
|
||||
@Test
|
||||
public void testTestOverride() {
|
||||
mCallDiagnosticService.setTestCallDiagnosticService(TEST_CDS_PACKAGE);
|
||||
mCallDiagnosticService.onCallAdded(mCall);
|
||||
|
||||
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
|
||||
verify(mContextProxy).bindServiceAsUser(intentCaptor.capture(),
|
||||
any(ServiceConnection.class), anyInt(), any(UserHandle.class));
|
||||
assertEquals(TEST_CDS_PACKAGE, intentCaptor.getValue().getPackage());
|
||||
}
|
||||
}
|
|
@ -67,6 +67,7 @@ import com.android.server.telecom.Call;
|
|||
import com.android.server.telecom.CallAudioManager;
|
||||
import com.android.server.telecom.CallAudioModeStateMachine;
|
||||
import com.android.server.telecom.CallAudioRouteStateMachine;
|
||||
import com.android.server.telecom.CallDiagnosticServiceController;
|
||||
import com.android.server.telecom.CallState;
|
||||
import com.android.server.telecom.CallerInfoLookupHelper;
|
||||
import com.android.server.telecom.CallsManager;
|
||||
|
@ -196,6 +197,7 @@ public class CallsManagerTest extends TelecomTestCase {
|
|||
@Mock private CallAudioRouteStateMachine.Factory mCallAudioRouteStateMachineFactory;
|
||||
@Mock private CallAudioModeStateMachine mCallAudioModeStateMachine;
|
||||
@Mock private CallAudioModeStateMachine.Factory mCallAudioModeStateMachineFactory;
|
||||
@Mock private CallDiagnosticServiceController mCallDiagnosticServiceController;
|
||||
@Mock private BluetoothStateReceiver mBluetoothStateReceiver;
|
||||
@Mock private RoleManagerAdapter mRoleManagerAdapter;
|
||||
@Mock private ToastFactory mToastFactory;
|
||||
|
@ -253,6 +255,7 @@ public class CallsManagerTest extends TelecomTestCase {
|
|||
mCallAudioRouteStateMachineFactory,
|
||||
mCallAudioModeStateMachineFactory,
|
||||
mInCallControllerFactory,
|
||||
mCallDiagnosticServiceController,
|
||||
mRoleManagerAdapter,
|
||||
mToastFactory);
|
||||
|
||||
|
|
Loading…
Reference in New Issue