diff options
| author | Xin Li <delphij@google.com> | 2021-10-07 23:50:24 +0000 |
|---|---|---|
| committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2021-10-07 23:50:24 +0000 |
| commit | 4dffc62f2b5462ddcd01051c9b479e8e91055376 (patch) | |
| tree | 80afdab2635af0425aca44dc2df05dd652ebcaa7 /src/com/android/server | |
| parent | 66273f82ba3a6097a4189d453df35aac4b1407b5 (diff) | |
| parent | a920c1b1ca4e2a7d34ea0a08fb06b7cca0a3e8fd (diff) | |
| download | platform_packages_services_Telecomm-master.tar.gz platform_packages_services_Telecomm-master.tar.bz2 platform_packages_services_Telecomm-master.zip | |
Diffstat (limited to 'src/com/android/server')
37 files changed, 1703 insertions, 723 deletions
diff --git a/src/com/android/server/telecom/BluetoothAdapterProxy.java b/src/com/android/server/telecom/BluetoothAdapterProxy.java deleted file mode 100644 index ee9cde3f0..000000000 --- a/src/com/android/server/telecom/BluetoothAdapterProxy.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2016 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.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothProfile; -import android.content.Context; - -/** - * Proxy class used so that BluetoothAdapter can be mocked for testing. - */ -public class BluetoothAdapterProxy { - private BluetoothAdapter mBluetoothAdapter; - - public BluetoothAdapterProxy() { - mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); - } - - public boolean getProfileProxy(Context context, BluetoothProfile.ServiceListener listener, - int profile) { - if (mBluetoothAdapter == null) { - return false; - } - return mBluetoothAdapter.getProfileProxy(context, listener, profile); - } - - public boolean setActiveDevice(BluetoothDevice device, int profiles) { - if (mBluetoothAdapter == null) { - return false; - } - if (device != null) { - return mBluetoothAdapter.setActiveDevice(device, profiles); - } else { - return mBluetoothAdapter.removeActiveDevice(profiles); - } - } -} diff --git a/src/com/android/server/telecom/BluetoothHeadsetProxy.java b/src/com/android/server/telecom/BluetoothHeadsetProxy.java deleted file mode 100644 index e4eed8707..000000000 --- a/src/com/android/server/telecom/BluetoothHeadsetProxy.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2015 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.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothHeadset; - -import java.util.List; - -/** - * A proxy class that facilitates testing of the BluetoothPhoneServiceImpl class. - * - * This is necessary due to the "final" attribute of the BluetoothHeadset class. In order to - * test the correct functioning of the BluetoothPhoneServiceImpl class, the final class must be put - * into a container that can be mocked correctly. - */ -public class BluetoothHeadsetProxy { - - private BluetoothHeadset mBluetoothHeadset; - - public BluetoothHeadsetProxy(BluetoothHeadset headset) { - mBluetoothHeadset = headset; - } - - public List<BluetoothDevice> getConnectedDevices() { - return mBluetoothHeadset.getConnectedDevices(); - } - - public int getConnectionState(BluetoothDevice device) { - return mBluetoothHeadset.getConnectionState(device); - } - - public int getAudioState(BluetoothDevice device) { - return mBluetoothHeadset.getAudioState(device); - } - - public boolean connectAudio() { - return mBluetoothHeadset.connectAudio(); - } - - public boolean setActiveDevice(BluetoothDevice device) { - return mBluetoothHeadset.setActiveDevice(device); - } - - public BluetoothDevice getActiveDevice() { - return mBluetoothHeadset.getActiveDevice(); - } - - public boolean isAudioOn() { - return mBluetoothHeadset.isAudioOn(); - } - - public boolean disconnectAudio() { - return mBluetoothHeadset.disconnectAudio(); - } - - public boolean isInbandRingingEnabled() { - return mBluetoothHeadset.isInbandRingingEnabled(); - } -} diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java index ae0719b88..ed6d223ca 100644 --- a/src/com/android/server/telecom/Call.java +++ b/src/com/android/server/telecom/Call.java @@ -39,14 +39,14 @@ import android.provider.CallLog; import android.provider.ContactsContract.Contacts; import android.telecom.BluetoothCallQualityReport; import android.telecom.CallAudioState; +import android.telecom.CallDiagnosticService; +import android.telecom.CallDiagnostics; 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; import android.telecom.Log; import android.telecom.Logging.EventManager; import android.telecom.ParcelableConference; @@ -57,11 +57,12 @@ import android.telecom.Response; import android.telecom.StatusHints; import android.telecom.TelecomManager; import android.telecom.VideoProfile; +import android.telephony.CallQuality; import android.telephony.PhoneNumberUtils; import android.telephony.TelephonyManager; import android.telephony.emergency.EmergencyNumber; +import android.telephony.ims.ImsReasonInfo; import android.text.TextUtils; -import android.util.ArrayMap; import android.widget.Toast; import com.android.internal.annotations.VisibleForTesting; @@ -81,7 +82,9 @@ import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; /** * Encapsulates all aspects of a given phone call throughout its lifecycle, starting @@ -160,6 +163,8 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable, void onHandoverComplete(Call call); void onBluetoothCallQualityReport(Call call, BluetoothCallQualityReport report); void onReceivedDeviceToDeviceMessage(Call call, int messageType, int messageValue); + void onReceivedCallQualityReport(Call call, CallQuality callQuality); + void onCallerNumberVerificationStatusChanged(Call call, int callerNumberVerificationStatus); } public abstract static class ListenerBase implements Listener { @@ -252,6 +257,11 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable, public void onBluetoothCallQualityReport(Call call, BluetoothCallQualityReport report) {} @Override public void onReceivedDeviceToDeviceMessage(Call call, int messageType, int messageValue) {} + @Override + public void onReceivedCallQualityReport(Call call, CallQuality callQuality) {} + @Override + public void onCallerNumberVerificationStatusChanged(Call call, + int callerNumberVerificationStatus) {} } private final CallerInfoLookupHelper.OnQueryCompleteListener mCallerInfoQueryListener = @@ -662,6 +672,22 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable, private boolean mIsSimCall; /** + * Set to {@code true} if we received a valid response ({@code null} or otherwise) from + * the {@link CallDiagnostics#onCallDisconnected(ImsReasonInfo)} or + * {@link CallDiagnostics#onCallDisconnected(int, int)} calls. This is used to detect a timeout + * when awaiting a response from the call diagnostic service. + */ + private boolean mReceivedCallDiagnosticPostCallResponse = false; + + /** + * {@link CompletableFuture} used to delay posting disconnection and removal to a call until + * after a {@link CallDiagnosticService} is able to handle the disconnection and provide a + * disconnect message via {@link CallDiagnostics#onCallDisconnected(ImsReasonInfo)} or + * {@link CallDiagnostics#onCallDisconnected(int, int)}. + */ + private CompletableFuture<Boolean> mDisconnectFuture; + + /** * Persists the specified parameters and initializes the new instance. * @param context The context. * @param repository The connection service repository. @@ -1092,8 +1118,29 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable, } } + /** + * Handles an incoming overridden disconnect message for this call. + * + * We only care if the disconnect is handled via a future. + * @param message the overridden disconnect message. + */ public void handleOverrideDisconnectMessage(@Nullable CharSequence message) { + Log.i(this, "handleOverrideDisconnectMessage; callid=%s, msg=%s", getId(), message); + + if (isDisconnectHandledViaFuture()) { + mReceivedCallDiagnosticPostCallResponse = true; + if (message != null) { + Log.addEvent(this, LogUtils.Events.OVERRIDE_DISCONNECT_MESSAGE, message); + // Replace the existing disconnect cause in this call + setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.ERROR, message, + message, null)); + } + mDisconnectFuture.complete(true); + } else { + Log.w(this, "handleOverrideDisconnectMessage; callid=%s - got override when unbound", + getId()); + } } /** @@ -1280,6 +1327,8 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable, public void setCallerNumberVerificationStatus( @Connection.VerificationStatus int callerNumberVerificationStatus) { mCallerNumberVerificationStatus = callerNumberVerificationStatus; + mListeners.forEach(l -> l.onCallerNumberVerificationStatusChanged(this, + callerNumberVerificationStatus)); } public @Connection.VerificationStatus int getCallerNumberVerificationStatus() { @@ -1316,6 +1365,9 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable, } catch (IllegalStateException ise) { Log.e(this, ise, "setHandle: can't determine if number is emergency"); mIsEmergencyCall = false; + } catch (RuntimeException r) { + Log.e(this, r, "setHandle: can't determine if number is emergency"); + mIsEmergencyCall = false; } mAnalytics.setCallIsEmergency(mIsEmergencyCall); } @@ -1340,6 +1392,8 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable, number.equals(eNumber.getNumber())); } catch (IllegalStateException ise) { return false; + } catch (RuntimeException r) { + return false; } } @@ -2665,7 +2719,8 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable, return mState == CallState.ACTIVE; } - Bundle getExtras() { + @VisibleForTesting + public Bundle getExtras() { return mExtras; } @@ -2705,6 +2760,16 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable, setOriginalConnectionId(extras.getString(Connection.EXTRA_ORIGINAL_CONNECTION_ID)); } + if (extras.containsKey(Connection.EXTRA_CALLER_NUMBER_VERIFICATION_STATUS) + && source == SOURCE_CONNECTION_SERVICE) { + int callerNumberVerificationStatus = + extras.getInt(Connection.EXTRA_CALLER_NUMBER_VERIFICATION_STATUS); + if (mCallerNumberVerificationStatus != callerNumberVerificationStatus) { + Log.addEvent(this, LogUtils.Events.VERSTAT_CHANGED, callerNumberVerificationStatus); + setCallerNumberVerificationStatus(callerNumberVerificationStatus); + } + } + // The remote connection service API can track the phone account which was originally // requested to create a connection via the remote connection service API; we store that so // we have some visibility into how a call was actually placed. @@ -3726,7 +3791,10 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable, * @param extras The extras. */ public void onConnectionEvent(String event, Bundle extras) { - Log.addEvent(this, LogUtils.Events.CONNECTION_EVENT, event); + // Don't log call quality reports; they're quite frequent and will clog the log. + if (!Connection.EVENT_CALL_QUALITY_REPORT.equals(event)) { + Log.addEvent(this, LogUtils.Events.CONNECTION_EVENT, event); + } if (Connection.EVENT_ON_HOLD_TONE_START.equals(event)) { mIsRemotelyHeld = true; Log.addEvent(this, LogUtils.Events.REMOTELY_HELD); @@ -3760,6 +3828,12 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable, for (Listener l : mListeners) { l.onReceivedDeviceToDeviceMessage(this, messageType, messageValue); } + } else if (Connection.EVENT_CALL_QUALITY_REPORT.equals(event) + && extras != null && extras.containsKey(Connection.EXTRA_CALL_QUALITY_REPORT)) { + CallQuality callQuality = extras.getParcelable(Connection.EXTRA_CALL_QUALITY_REPORT); + for (Listener l : mListeners) { + l.onReceivedCallQualityReport(this, callQuality); + } } else { for (Listener l : mListeners) { l.onConnectionEvent(this, event, extras); @@ -3925,6 +3999,10 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable, mIsUsingCallFiltering = isUsingCallFiltering; } + public boolean isUsingCallFiltering() { + return mIsUsingCallFiltering; + } + /** * Returns whether or not Volte call was used. * @@ -3965,7 +4043,7 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable, * @param message the message type to send. * @param value the value for the message. */ - public void sendDeviceToDeviceMessage(@DiagnosticCall.MessageType int message, int value) { + public void sendDeviceToDeviceMessage(@CallDiagnostics.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); @@ -4093,4 +4171,69 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable, public boolean isSimCall() { return mIsSimCall; } + + /** + * Sets whether this is a sim call or not. + * @param isSimCall {@code true} if this is a SIM call, {@code false} otherwise. + */ + public void setIsSimCall(boolean isSimCall) { + mIsSimCall = isSimCall; + } + + /** + * Initializes a disconnect future which is used to chain up pending operations which take + * place when the {@link CallDiagnosticService} returns the result of the + * {@link CallDiagnostics#onCallDisconnected(int, int)} or + * {@link CallDiagnostics#onCallDisconnected(ImsReasonInfo)} invocation via + * {@link CallDiagnosticServiceAdapter}. If no {@link CallDiagnosticService} is in use, we + * would not try to make a disconnect future. + * @param timeoutMillis Timeout we use for waiting for the response. + * @return the {@link CompletableFuture}. + */ + public CompletableFuture<Boolean> initializeDisconnectFuture(long timeoutMillis) { + if (mDisconnectFuture == null) { + mDisconnectFuture = new CompletableFuture<Boolean>() + .completeOnTimeout(false, timeoutMillis, TimeUnit.MILLISECONDS); + // After all the chained stuff we will report where the CDS timed out. + mDisconnectFuture.thenRunAsync(() -> { + if (!mReceivedCallDiagnosticPostCallResponse) { + Log.addEvent(this, LogUtils.Events.CALL_DIAGNOSTIC_SERVICE_TIMEOUT); + } + // Clear the future as a final step. + mDisconnectFuture = null; + }, + new LoggedHandlerExecutor(mHandler, "C.iDF", mLock)) + .exceptionally((throwable) -> { + Log.e(this, throwable, "Error while executing disconnect future"); + return null; + }); + } + return mDisconnectFuture; + } + + /** + * @return the disconnect future, if initialized. Used for chaining operations after creation. + */ + public CompletableFuture<Boolean> getDisconnectFuture() { + return mDisconnectFuture; + } + + /** + * @return {@code true} if disconnection and removal is handled via a future, or {@code false} + * if this is handled immediately. + */ + public boolean isDisconnectHandledViaFuture() { + return mDisconnectFuture != null; + } + + /** + * Perform any cleanup on this call as a result of a {@link TelecomServiceImpl} + * {@code cleanupStuckCalls} request. + */ + public void cleanup() { + if (mDisconnectFuture != null) { + mDisconnectFuture.complete(false); + mDisconnectFuture = null; + } + } } diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java index 7bf94ed57..e5a6eccfd 100644 --- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java +++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java @@ -1347,6 +1347,15 @@ public class CallAudioRouteStateMachine extends StateMachine { } else { sendInternalMessage(MUTE_EXTERNALLY_CHANGED); } + } else if (AudioManager.STREAM_MUTE_CHANGED_ACTION.equals(intent.getAction())) { + int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); + boolean isStreamMuted = intent.getBooleanExtra( + AudioManager.EXTRA_STREAM_VOLUME_MUTED, false); + + if (streamType == AudioManager.STREAM_RING && !isStreamMuted) { + Log.i(this, "Ring stream was un-muted."); + mCallAudioManager.onRingerModeChange(); + } } else { Log.w(this, "Received non-mute-change intent"); } @@ -1530,6 +1539,8 @@ public class CallAudioRouteStateMachine extends StateMachine { mWasOnSpeaker = false; mContext.registerReceiver(mMuteChangeReceiver, new IntentFilter(AudioManager.ACTION_MICROPHONE_MUTE_CHANGED)); + mContext.registerReceiver(mMuteChangeReceiver, + new IntentFilter(AudioManager.STREAM_MUTE_CHANGED_ACTION)); mContext.registerReceiver(mSpeakerPhoneChangeReceiver, new IntentFilter(AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED)); diff --git a/src/com/android/server/telecom/CallDiagnosticServiceAdapter.java b/src/com/android/server/telecom/CallDiagnosticServiceAdapter.java index 79a94d3bd..862386648 100644 --- a/src/com/android/server/telecom/CallDiagnosticServiceAdapter.java +++ b/src/com/android/server/telecom/CallDiagnosticServiceAdapter.java @@ -20,11 +20,10 @@ import android.annotation.NonNull; import android.os.Binder; import android.os.RemoteException; import android.telecom.CallDiagnosticService; -import android.telecom.DiagnosticCall; +import android.telecom.CallDiagnostics; 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 @@ -34,7 +33,7 @@ public class CallDiagnosticServiceAdapter extends ICallDiagnosticServiceAdapter. 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, + void sendDeviceToDeviceMessage(String callId, @CallDiagnostics.MessageType int message, int value); void overrideDisconnectMessage(String callId, CharSequence message); } @@ -91,7 +90,7 @@ public class CallDiagnosticServiceAdapter extends ICallDiagnosticServiceAdapter. } @Override - public void sendDeviceToDeviceMessage(String callId, @DiagnosticCall.MessageType int message, + public void sendDeviceToDeviceMessage(String callId, @CallDiagnostics.MessageType int message, int value) throws RemoteException { try { diff --git a/src/com/android/server/telecom/CallDiagnosticServiceController.java b/src/com/android/server/telecom/CallDiagnosticServiceController.java index 943a176d0..d8ee475e7 100644 --- a/src/com/android/server/telecom/CallDiagnosticServiceController.java +++ b/src/com/android/server/telecom/CallDiagnosticServiceController.java @@ -35,17 +35,18 @@ import android.telecom.BluetoothCallQualityReport; import android.telecom.CallAudioState; import android.telecom.CallDiagnosticService; import android.telecom.ConnectionService; -import android.telecom.DiagnosticCall; +import android.telecom.CallDiagnostics; +import android.telecom.DisconnectCause; import android.telecom.InCallService; import android.telecom.Log; import android.telecom.ParcelableCall; +import android.telephony.CallQuality; 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; /** @@ -154,6 +155,16 @@ public class CallDiagnosticServiceController extends CallsManagerListenerBase { public void onReceivedDeviceToDeviceMessage(Call call, int messageType, int messageValue) { handleReceivedDeviceToDeviceMessage(call, messageType, messageValue); } + + /** + * Handles an incoming {@link CallQuality} report from a {@link android.telecom.Connection}. + * @param call The call. + * @param callQualityReport The call quality report. + */ + @Override + public void onReceivedCallQualityReport(Call call, CallQuality callQualityReport) { + handleCallQualityReport(call, callQualityReport); + } }; /** @@ -218,6 +229,7 @@ public class CallDiagnosticServiceController extends CallsManagerListenerBase { private final String mPackageName; private final ContextProxy mContextProxy; + private InCallTonePlayer.Factory mPlayerFactory; private String mTestPackageName; private CallDiagnosticServiceConnection mConnection; private CallDiagnosticServiceAdapter mAdapter; @@ -233,6 +245,14 @@ public class CallDiagnosticServiceController extends CallsManagerListenerBase { } /** + * Sets the current {@link InCallTonePlayer.Factory} for this instance. + * @param factory the factory. + */ + public void setInCallTonePlayerFactory(InCallTonePlayer.Factory factory) { + mPlayerFactory = factory; + } + + /** * 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. @@ -255,6 +275,32 @@ public class CallDiagnosticServiceController extends CallsManagerListenerBase { } /** + * Handles a newly disconnected call signalled from {@link CallsManager}. + * @param call The call + * @param disconnectCause The disconnect cause + * @return {@code true} if the {@link CallDiagnosticService} was sent the call, {@code false} + * if the call was not applicable to the CDS or if there was an issue sending it. + */ + public boolean onCallDisconnected(@NonNull Call call, + @NonNull DisconnectCause disconnectCause) { + if (!call.isSimCall() || call.isExternalCall()) { + Log.i(this, "onCallDisconnected: skipping call %s as non-sim or external.", + call.getId()); + return false; + } + String callId = mCallIdMapper.getCallId(call); + try { + if (isConnected()) { + mCallDiagnosticService.notifyCallDisconnected(callId, disconnectCause); + return true; + } + } catch (RemoteException e) { + Log.w(this, "onCallDisconnected: callId=%s, exception=%s", call.getId(), e); + } + return false; + } + + /** * 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. @@ -428,7 +474,7 @@ public class CallDiagnosticServiceController extends CallsManagerListenerBase { @Override public void sendDeviceToDeviceMessage(String callId, - @DiagnosticCall.MessageType int message, int value) { + @CallDiagnostics.MessageType int message, int value) { handleSendD2DMessage(callId, message, value); } @@ -471,8 +517,13 @@ public class CallDiagnosticServiceController extends CallsManagerListenerBase { callId, messageId, message); return; } - Log.i(this, "handleDisplayDiagnosticMessage: callId=%s; msg=%d/%s; invalid call", + Log.i(this, "handleDisplayDiagnosticMessage: callId=%s; msg=%d/%s", callId, messageId, message); + if (mPlayerFactory != null) { + // Play that tone! + mPlayerFactory.createPlayer(InCallTonePlayer.TONE_IN_CALL_QUALITY_NOTIFICATION) + .startTone(); + } call.displayDiagnosticMessage(messageId, message); } @@ -501,7 +552,7 @@ public class CallDiagnosticServiceController extends CallsManagerListenerBase { * @param value The message value. */ private void handleSendD2DMessage(@NonNull String callId, - @DiagnosticCall.MessageType int message, int value) { + @CallDiagnostics.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, @@ -515,7 +566,7 @@ public class CallDiagnosticServiceController extends CallsManagerListenerBase { /** * 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)}. + * {@link CallDiagnosticService} via {@link CallDiagnostics#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. */ @@ -569,7 +620,7 @@ public class CallDiagnosticServiceController extends CallsManagerListenerBase { /** * @return {@code true} if the call diagnostic service is bound/connected. */ - private boolean isConnected() { + public boolean isConnected() { return mCallDiagnosticService != null; } @@ -625,6 +676,23 @@ public class CallDiagnosticServiceController extends CallsManagerListenerBase { } /** + * Handles a reported {@link CallQuality} report from a {@link android.telecom.Connection}. + * @param call The call the report originated from. + * @param callQualityReport The {@link CallQuality} report. + */ + private void handleCallQualityReport(@NonNull Call call, + @NonNull CallQuality callQualityReport) { + try { + if (isConnected()) { + mCallDiagnosticService.callQualityChanged(call.getId(), callQualityReport); + } + } catch (RemoteException e) { + Log.w(this, "handleCallQualityReport: 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. diff --git a/src/com/android/server/telecom/CallLogManager.java b/src/com/android/server/telecom/CallLogManager.java index 3cec6187b..0ec236274 100755 --- a/src/com/android/server/telecom/CallLogManager.java +++ b/src/com/android/server/telecom/CallLogManager.java @@ -16,7 +16,7 @@ package com.android.server.telecom; -import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED; +import static android.provider.CallLog.Calls.BLOCK_REASON_NOT_BLOCKED; import static android.telephony.CarrierConfigManager.KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_BOOL; import android.annotation.Nullable; @@ -24,25 +24,26 @@ import android.content.Context; import android.content.Intent; import android.location.Country; import android.location.CountryDetector; +import android.location.Location; import android.net.Uri; import android.os.AsyncTask; import android.os.Looper; import android.os.UserHandle; import android.os.PersistableBundle; +import android.provider.CallLog; import android.provider.CallLog.Calls; import android.telecom.Connection; import android.telecom.DisconnectCause; import android.telecom.Log; import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; +import android.telecom.TelecomManager; import android.telecom.VideoProfile; import android.telephony.CarrierConfigManager; import android.telephony.PhoneNumberUtils; import android.telephony.SubscriptionManager; -// TODO: Needed for move to system service: import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; -import android.telecom.CallerInfo; import com.android.server.telecom.callfiltering.CallFilteringResult; import java.util.Arrays; @@ -66,71 +67,19 @@ public final class CallLogManager extends CallsManagerListenerBase { * Parameter object to hold the arguments to add a call in the call log DB. */ private static class AddCallArgs { - /** - * @param callerInfo Caller details. - * @param number The phone number to be logged. - * @param presentation Number presentation of the phone number to be logged. - * @param callType The type of call (e.g INCOMING_TYPE). @see - * {@link android.provider.CallLog} for the list of values. - * @param features The features of the call (e.g. FEATURES_VIDEO). @see - * {@link android.provider.CallLog} for the list of values. - * @param creationDate Time when the call was created (milliseconds since epoch). - * @param durationInMillis Duration of the call (milliseconds). - * @param dataUsage Data usage in bytes, or null if not applicable. - * @param isRead Indicates if the entry has been read or not. - * @param logCallCompletedListener optional callback called after the call is logged. - */ - public AddCallArgs(Context context, CallerInfo callerInfo, String number, - String postDialDigits, String viaNumber, int presentation, int callType, - int features, PhoneAccountHandle accountHandle, long creationDate, - long durationInMillis, Long dataUsage, UserHandle initiatingUser, boolean isRead, - @Nullable LogCallCompletedListener logCallCompletedListener, int callBlockReason, - CharSequence callScreeningAppName, String callScreeningComponentName, - long missedReason) { + public AddCallArgs(Context context, CallLog.AddCallParams params, + @Nullable LogCallCompletedListener logCallCompletedListener) { this.context = context; - this.callerInfo = callerInfo; - this.number = number; - this.postDialDigits = postDialDigits; - this.viaNumber = viaNumber; - this.presentation = presentation; - this.callType = callType; - this.features = features; - this.accountHandle = accountHandle; - this.timestamp = creationDate; - this.durationInSec = (int)(durationInMillis / 1000); - this.dataUsage = dataUsage; - this.initiatingUser = initiatingUser; - this.isRead = isRead; + this.params = params; this.logCallCompletedListener = logCallCompletedListener; - this.callBockReason = callBlockReason; - this.callScreeningAppName = callScreeningAppName; - this.callScreeningComponentName = callScreeningComponentName; - this.missedReason = missedReason; + } // Since the members are accessed directly, we don't use the // mXxxx notation. public final Context context; - public final CallerInfo callerInfo; - public final String number; - public final String postDialDigits; - public final String viaNumber; - public final int presentation; - public final int callType; - public final int features; - public final PhoneAccountHandle accountHandle; - public final long timestamp; - public final int durationInSec; - public final Long dataUsage; - public final UserHandle initiatingUser; - public final boolean isRead; - + public final CallLog.AddCallParams params; @Nullable public final LogCallCompletedListener logCallCompletedListener; - - public final int callBockReason; - public final CharSequence callScreeningAppName; - public final String callScreeningComponentName; - public final long missedReason; } private static final String TAG = CallLogManager.class.getSimpleName(); @@ -315,47 +264,50 @@ public final class CallLogManager extends CallsManagerListenerBase { */ void logCall(Call call, int callLogType, @Nullable LogCallCompletedListener logCallCompletedListener, CallFilteringResult result) { - long creationTime; + CallLog.AddCallParams.AddCallParametersBuilder paramBuilder = + new CallLog.AddCallParams.AddCallParametersBuilder(); if (call.getConnectTimeMillis() != 0 && call.getConnectTimeMillis() < call.getCreationTimeMillis()) { // If connected time is available, use connected time. The connected time might be // earlier than created time since it might come from carrier sent special SMS to // notifier user earlier missed call. - creationTime = call.getConnectTimeMillis(); + paramBuilder.setStart(call.getConnectTimeMillis()); } else { - creationTime = call.getCreationTimeMillis(); + paramBuilder.setStart(call.getCreationTimeMillis()); } - final long age = call.getAgeMillis(); + paramBuilder.setDuration((int) (call.getAgeMillis() / 1000)); - final String logNumber = getLogNumber(call); + String logNumber = getLogNumber(call); + paramBuilder.setNumber(logNumber); Log.d(TAG, "logNumber set to: %s", Log.pii(logNumber)); - final PhoneAccountHandle emergencyAccountHandle = - TelephonyUtil.getDefaultEmergencyPhoneAccount().getAccountHandle(); - String formattedViaNumber = PhoneNumberUtils.formatNumber(call.getViaNumber(), getCountryIso()); formattedViaNumber = (formattedViaNumber != null) ? formattedViaNumber : call.getViaNumber(); + paramBuilder.setViaNumber(formattedViaNumber); + final PhoneAccountHandle emergencyAccountHandle = + TelephonyUtil.getDefaultEmergencyPhoneAccount().getAccountHandle(); PhoneAccountHandle accountHandle = call.getTargetPhoneAccount(); if (emergencyAccountHandle.equals(accountHandle)) { accountHandle = null; } + paramBuilder.setAccountHandle(accountHandle); - Long callDataUsage = call.getCallDataUsage() == Call.DATA_USAGE_NOT_SET ? null : - call.getCallDataUsage(); + paramBuilder.setDataUsage(call.getCallDataUsage() == Call.DATA_USAGE_NOT_SET + ? Long.MIN_VALUE : call.getCallDataUsage()); - int callFeatures = getCallFeatures(call.getVideoStateHistory(), + paramBuilder.setFeatures(getCallFeatures(call.getVideoStateHistory(), call.getDisconnectCause().getCode() == DisconnectCause.CALL_PULLED, call.wasHighDefAudio(), call.wasWifi(), (call.getConnectionProperties() & Connection.PROPERTY_ASSISTED_DIALING) == Connection.PROPERTY_ASSISTED_DIALING, call.wasEverRttCall(), - call.wasVolte()); + call.wasVolte())); if (result == null) { result = new CallFilteringResult.Builder() @@ -363,67 +315,82 @@ public final class CallLogManager extends CallsManagerListenerBase { .setCallScreeningComponentName(call.getCallScreeningComponentName()) .build(); } - if (callLogType == Calls.BLOCKED_TYPE || callLogType == Calls.MISSED_TYPE) { - logCall(call.getCallerInfo(), logNumber, call.getPostDialDigits(), formattedViaNumber, - call.getHandlePresentation(), callLogType, callFeatures, accountHandle, - creationTime, age, callDataUsage, call.isEmergencyCall(), - call.getInitiatingUser(), call.isSelfManaged(), logCallCompletedListener, - result.mCallBlockReason, result.mCallScreeningAppName, - result.mCallScreeningComponentName, call.getMissedReason()); + paramBuilder.setCallBlockReason(result.mCallBlockReason); + paramBuilder.setCallScreeningComponentName(result.mCallScreeningComponentName); + paramBuilder.setCallScreeningAppName(result.mCallScreeningAppName); } else { - logCall(call.getCallerInfo(), logNumber, call.getPostDialDigits(), formattedViaNumber, - call.getHandlePresentation(), callLogType, callFeatures, accountHandle, - creationTime, age, callDataUsage, call.isEmergencyCall(), - call.getInitiatingUser(), call.isSelfManaged(), logCallCompletedListener, - Calls.BLOCK_REASON_NOT_BLOCKED, null /*callScreeningAppName*/, - null /*callScreeningComponentName*/, call.getMissedReason()); + paramBuilder.setCallBlockReason(BLOCK_REASON_NOT_BLOCKED); } - } - /** - * Inserts a call into the call log, based on the parameters passed in. - * - * @param callerInfo Caller details. - * @param number The number the call was made to or from. - * @param postDialDigits The post-dial digits that were dialed after the number, - * if it was an outgoing call. Otherwise ''. - * @param presentation - * @param callType The type of call. - * @param features The features of the call. - * @param start The start time of the call, in milliseconds. - * @param duration The duration of the call, in milliseconds. - * @param dataUsage The data usage for the call, null if not applicable. - * @param isEmergency {@code true} if this is an emergency call, {@code false} otherwise. - * @param logCallCompletedListener optional callback called after the call is logged. - * @param initiatingUser The user the call was initiated under. - * @param isSelfManaged {@code true} if this is a self-managed call, {@code false} otherwise. - * @param callBlockReason The reason why the call is blocked. - * @param callScreeningAppName The call screening application name which block the call. - * @param callScreeningComponentName The call screening component name which block the call. - * @param missedReason The encoded information about reasons for missed call. - */ - private void logCall( - CallerInfo callerInfo, - String number, - String postDialDigits, - String viaNumber, - int presentation, - int callType, - int features, - PhoneAccountHandle accountHandle, - long start, - long duration, - Long dataUsage, - boolean isEmergency, - UserHandle initiatingUser, - boolean isSelfManaged, - @Nullable LogCallCompletedListener logCallCompletedListener, - int callBlockReason, - CharSequence callScreeningAppName, - String callScreeningComponentName, - long missedReason) { + PhoneAccount phoneAccount = mPhoneAccountRegistrar.getPhoneAccountUnchecked(accountHandle); + UserHandle initiatingUser = call.getInitiatingUser(); + if (phoneAccount != null && + phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) { + if (initiatingUser != null && + UserUtil.isManagedProfile(mContext, initiatingUser)) { + paramBuilder.setUserToBeInsertedTo(initiatingUser); + paramBuilder.setAddForAllUsers(false); + } else { + paramBuilder.setAddForAllUsers(true); + } + } else { + if (accountHandle == null) { + paramBuilder.setAddForAllUsers(true); + } else { + paramBuilder.setUserToBeInsertedTo(accountHandle.getUserHandle()); + paramBuilder.setAddForAllUsers(accountHandle.getUserHandle() == null); + } + } + if (call.getIntentExtras() != null) { + if (call.getIntentExtras().containsKey(TelecomManager.EXTRA_PRIORITY)) { + paramBuilder.setPriority(call.getIntentExtras() + .getInt(TelecomManager.EXTRA_PRIORITY)); + } + if (call.getIntentExtras().containsKey(TelecomManager.EXTRA_CALL_SUBJECT)) { + paramBuilder.setSubject(call.getIntentExtras() + .getString(TelecomManager.EXTRA_CALL_SUBJECT)); + } + if (call.getIntentExtras().containsKey(TelecomManager.EXTRA_PICTURE_URI)) { + paramBuilder.setPictureUri(call.getIntentExtras() + .getParcelable(TelecomManager.EXTRA_PICTURE_URI)); + } + // The picture uri can end up either in extras or in intent extras due to how these + // two bundles are set. For incoming calls they're in extras, but for outgoing calls + // they're in intentExtras. + if (call.getExtras() != null + && call.getExtras().containsKey(TelecomManager.EXTRA_PICTURE_URI)) { + paramBuilder.setPictureUri(call.getExtras() + .getParcelable(TelecomManager.EXTRA_PICTURE_URI)); + } + if (call.getIntentExtras().containsKey(TelecomManager.EXTRA_LOCATION)) { + Location l = call.getIntentExtras().getParcelable(TelecomManager.EXTRA_LOCATION); + if (l != null) { + paramBuilder.setLatitude(l.getLatitude()); + paramBuilder.setLongitude(l.getLongitude()); + } + } + } + + paramBuilder.setCallerInfo(call.getCallerInfo()); + paramBuilder.setPostDialDigits(call.getPostDialDigits()); + paramBuilder.setPresentation(call.getHandlePresentation()); + paramBuilder.setCallType(callLogType); + paramBuilder.setIsRead(call.isSelfManaged()); + paramBuilder.setMissedReason(call.getMissedReason()); + + sendAddCallBroadcast(callLogType, call.getAgeMillis()); + boolean okayToLog = + okayToLogCall(accountHandle, logNumber, call.isEmergencyCall()); + if (okayToLog) { + AddCallArgs args = new AddCallArgs(mContext, paramBuilder.build(), + logCallCompletedListener); + logCallAsync(args); + } + } + + boolean okayToLogCall(PhoneAccountHandle accountHandle, String number, boolean isEmergency) { // On some devices, to avoid accidental redialing of emergency numbers, we *never* log // emergency calls to the Call Log. (This behavior is set on a per-product basis, based // on carrier requirements.) @@ -438,29 +405,8 @@ public final class CallLogManager extends CallsManagerListenerBase { } // Don't log emergency numbers if the device doesn't allow it. - final boolean isOkToLogThisCall = (!isEmergency || okToLogEmergencyNumber) + return (!isEmergency || okToLogEmergencyNumber) && !isUnloggableNumber(number, configBundle); - - sendAddCallBroadcast(callType, duration); - - if (isOkToLogThisCall) { - Log.d(TAG, "Logging Call log entry: " + callerInfo + ", " - + Log.pii(number) + "," + presentation + ", " + callType - + ", " + start + ", " + duration); - boolean isRead = false; - if (isSelfManaged) { - // Mark self-managed calls are read since they're being handled by their own app. - // Their inclusion in the call log is informational only. - isRead = true; - } - AddCallArgs args = new AddCallArgs(mContext, callerInfo, number, postDialDigits, - viaNumber, presentation, callType, features, accountHandle, start, duration, - dataUsage, initiatingUser, isRead, logCallCompletedListener, callBlockReason, - callScreeningAppName, callScreeningComponentName, missedReason); - logCallAsync(args); - } else { - Log.d(TAG, "Not adding emergency call to call log."); - } } private boolean isUnloggableNumber(String callNumber, PersistableBundle carrierConfig) { @@ -566,7 +512,7 @@ public final class CallLogManager extends CallsManagerListenerBase { mListeners[i] = c.logCallCompletedListener; try { // May block. - result[i] = addCall(c); + result[i] = Calls.addCall(c.context, c.params); } catch (Exception e) { // This is very rare but may happen in legitimate cases. // E.g. If the phone is encrypted and thus write request fails, it may cause @@ -582,37 +528,6 @@ public final class CallLogManager extends CallsManagerListenerBase { return result; } - private Uri addCall(AddCallArgs c) { - PhoneAccount phoneAccount = mPhoneAccountRegistrar - .getPhoneAccountUnchecked(c.accountHandle); - if (phoneAccount != null && - phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) { - if (c.initiatingUser != null && - UserUtil.isManagedProfile(mContext, c.initiatingUser)) { - return addCall(c, c.initiatingUser); - } else { - return addCall(c, null); - } - } else { - return addCall(c, c.accountHandle == null ? null : c.accountHandle.getUserHandle()); - } - } - - /** - * Insert the call to a specific user or all users except managed profile. - * @param c context - * @param userToBeInserted user handle of user that the call going be inserted to. null - * if insert to all users except managed profile. - */ - private Uri addCall(AddCallArgs c, UserHandle userToBeInserted) { - return Calls.addCall(c.callerInfo, c.context, c.number, c.postDialDigits, c.viaNumber, - c.presentation, c.callType, c.features, c.accountHandle, c.timestamp, - c.durationInSec, c.dataUsage, userToBeInserted == null, - userToBeInserted, c.isRead, c.callBockReason, c.callScreeningAppName, - c.callScreeningComponentName, c.missedReason); - } - - @Override protected void onPostExecute(Uri[] result) { for (int i = 0; i < result.length; i++) { diff --git a/src/com/android/server/telecom/CallScreeningServiceHelper.java b/src/com/android/server/telecom/CallScreeningServiceHelper.java index f02b9240c..94352506c 100644 --- a/src/com/android/server/telecom/CallScreeningServiceHelper.java +++ b/src/com/android/server/telecom/CallScreeningServiceHelper.java @@ -55,23 +55,8 @@ public class CallScreeningServiceHelper { } @Override - public void allowCall(String s) throws RemoteException { - unbindCallScreeningService(); - } - - @Override - public void silenceCall(String s) throws RemoteException { - unbindCallScreeningService(); - } - - @Override - public void screenCallFurther(String callId) throws RemoteException { - unbindCallScreeningService(); - } - - @Override - public void disallowCall(String s, boolean b, boolean b1, boolean b2, - ComponentName componentName) throws RemoteException { + public void onScreeningResponse(String callId, ComponentName componentName, + CallScreeningService.ParcelableCallResponse callResponse) { unbindCallScreeningService(); } diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java index 898b6a877..e4922b038 100755 --- a/src/com/android/server/telecom/CallsManager.java +++ b/src/com/android/server/telecom/CallsManager.java @@ -72,6 +72,7 @@ import android.provider.CallLog.Calls; import android.provider.Settings; import android.sysprop.TelephonyProperties; import android.telecom.CallAudioState; +import android.telecom.CallScreeningService; import android.telecom.CallerInfo; import android.telecom.Conference; import android.telecom.Connection; @@ -546,6 +547,7 @@ public class CallsManager extends Call.ListenerBase systemStateHelper, defaultDialerCache, mTimeoutsAdapter, emergencyCallHelper); mCallDiagnosticServiceController = callDiagnosticServiceController; + mCallDiagnosticServiceController.setInCallTonePlayerFactory(playerFactory); mRinger = new Ringer(playerFactory, context, systemSettingsUtil, asyncRingtonePlayer, ringtoneFactory, systemVibrator, new Ringer.VibrationEffectProxy(), mInCallController); @@ -767,6 +769,42 @@ public class CallsManager extends Call.ListenerBase return; } + // Inform our connection service that call filtering is done (if it was performed at all). + if (incomingCall.isUsingCallFiltering()) { + boolean isInContacts = incomingCall.getCallerInfo() != null + && incomingCall.getCallerInfo().contactExists; + Connection.CallFilteringCompletionInfo completionInfo = + new Connection.CallFilteringCompletionInfo(!result.shouldAllowCall, + isInContacts, + result.mCallScreeningResponse == null + ? null : result.mCallScreeningResponse.toCallResponse(), + result.mCallScreeningComponentName == null ? null + : ComponentName.unflattenFromString( + result.mCallScreeningComponentName)); + incomingCall.getConnectionService().onCallFilteringCompleted(incomingCall, + completionInfo); + } + + // Get rid of the call composer attachments that aren't wanted + if (result.mIsResponseFromSystemDialer && result.mCallScreeningResponse != null + && result.mCallScreeningResponse.getCallComposerAttachmentsToShow() >= 0) { + int attachmentMask = result.mCallScreeningResponse.getCallComposerAttachmentsToShow(); + if ((attachmentMask + & CallScreeningService.CallResponse.CALL_COMPOSER_ATTACHMENT_LOCATION) == 0) { + incomingCall.getIntentExtras().remove(TelecomManager.EXTRA_LOCATION); + } + + if ((attachmentMask + & CallScreeningService.CallResponse.CALL_COMPOSER_ATTACHMENT_SUBJECT) == 0) { + incomingCall.getIntentExtras().remove(TelecomManager.EXTRA_CALL_SUBJECT); + } + + if ((attachmentMask + & CallScreeningService.CallResponse.CALL_COMPOSER_ATTACHMENT_PRIORITY) == 0) { + incomingCall.getIntentExtras().remove(TelecomManager.EXTRA_PRIORITY); + } + } + if (result.shouldAllowCall) { incomingCall.setPostCallPackageName( getRoleManagerAdapter().getDefaultCallScreeningApp()); @@ -1250,8 +1288,8 @@ public class CallsManager extends Call.ListenerBase if (call.isSelfManaged()) { // Self managed calls will always be voip audio mode. call.setIsVoipAudioMode(true); - call.setVisibleToInCallService(phoneAccountExtras != null - && phoneAccountExtras.getBoolean( + call.setVisibleToInCallService(phoneAccountExtras == null + || phoneAccountExtras.getBoolean( PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, true)); } else { // Incoming call is managed, the active call is self-managed and can't be held. @@ -1526,8 +1564,8 @@ public class CallsManager extends Call.ListenerBase if (isSelfManaged) { // Self-managed calls will ALWAYS use voip audio mode. call.setIsVoipAudioMode(true); - call.setVisibleToInCallService(phoneAccountExtra != null - && phoneAccountExtra.getBoolean( + call.setVisibleToInCallService(phoneAccountExtra == null + || phoneAccountExtra.getBoolean( PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, true)); } call.setInitiatingUser(initiatingUser); @@ -1648,6 +1686,21 @@ public class CallsManager extends Call.ListenerBase } } + if (!finalCall.isEmergencyCall() && isInEmergencyCall()) { + Log.i(CallsManager.this, "Aborting call since there's an" + + " ongoing emergency call"); + // If the ongoing call is a managed call, we will prevent the outgoing + // call from dialing. + if (isConference) { + notifyCreateConferenceFailed(finalCall.getTargetPhoneAccount(), + finalCall); + } else { + notifyCreateConnectionFailed( + finalCall.getTargetPhoneAccount(), finalCall); + } + return CompletableFuture.completedFuture(null); + } + // If we can not supportany more active calls, our options are to move a call // to hold, disconnect a call, or cancel this call altogether. boolean isRoomForCall = finalCall.isEmergencyCall() ? @@ -2041,6 +2094,8 @@ public class CallsManager extends Call.ListenerBase handle.getSchemeSpecificPart()); } catch (IllegalStateException ise) { isPotentialEmergencyNumber = false; + } catch (RuntimeException r) { + isPotentialEmergencyNumber = false; } if (shouldCancelCall) { @@ -2218,8 +2273,16 @@ public class CallsManager extends Call.ListenerBase public void processRedirectedOutgoingCallAfterUserInteraction(String callId, String action) { Log.i(this, "processRedirectedOutgoingCallAfterUserInteraction for Call ID %s, action=%s", callId, action); - if (mPendingRedirectedOutgoingCall != null && mPendingRedirectedOutgoingCall.getId() - .equals(callId)) { + if (mPendingRedirectedOutgoingCall != null) { + String pendingCallId = mPendingRedirectedOutgoingCall.getId(); + if (!pendingCallId.equals(callId)) { + Log.i(this, "processRedirectedOutgoingCallAfterUserInteraction for new Call ID %s, " + + "cancel the previous pending Call with ID %s", callId, pendingCallId); + mPendingRedirectedOutgoingCall.disconnect("Another call redirection requested"); + mPendingRedirectedOutgoingCallInfo.remove(pendingCallId); + mPendingUnredirectedOutgoingCallInfo.remove(pendingCallId); + } + if (action.equals(TelecomBroadcastIntentProcessor.ACTION_PLACE_REDIRECTED_CALL)) { mHandler.post(mPendingRedirectedOutgoingCallInfo.get(callId).prepare()); } else if (action.equals( @@ -2892,8 +2955,8 @@ public class CallsManager extends Call.ListenerBase } private boolean isRttSettingOn(PhoneAccountHandle handle) { - boolean isRttModeSettingOn = Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.RTT_CALLING_MODE, 0) != 0; + boolean isRttModeSettingOn = Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.RTT_CALLING_MODE, 0, mContext.getUserId()) != 0; // If the carrier config says that we should ignore the RTT mode setting from the user, // assume that it's off (i.e. only make an RTT call if it's requested through the extra). boolean shouldIgnoreRttModeSetting = getCarrierConfigForPhoneAccount(handle) @@ -2971,6 +3034,7 @@ public class CallsManager extends Call.ListenerBase */ boolean holdActiveCallForNewCall(Call call) { Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall(); + Log.i(this, "holdActiveCallForNewCall, newCall: %s, activeCall: %s", call, activeCall); if (activeCall != null && activeCall != call) { if (canHold(activeCall)) { activeCall.hold(); @@ -3026,6 +3090,7 @@ public class CallsManager extends Call.ListenerBase @VisibleForTesting public void markCallAsActive(Call call) { + Log.i(this, "markCallAsActive, isSelfManaged: " + call.isSelfManaged()); if (call.isSelfManaged()) { // backward compatibility, the self-managed connection service will set the call state // to active directly. We should hold or disconnect the current active call based on the @@ -3077,27 +3142,83 @@ public class CallsManager extends Call.ListenerBase // be marked as missed. call.setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.MISSED)); } - call.setDisconnectCause(disconnectCause); - setCallState(call, CallState.DISCONNECTED, "disconnected set explicitly"); - if(oldState == CallState.NEW && disconnectCause.getCode() == DisconnectCause.MISSED) { + // If a call diagnostic service is in use, we will log the original telephony-provided + // disconnect cause, inform the CDS of the disconnection, and then chain the update of the + // call state until AFTER the CDS reports it's result back. + if ((oldState == CallState.ACTIVE || oldState == CallState.DIALING) + && disconnectCause.getCode() != DisconnectCause.MISSED + && mCallDiagnosticServiceController.isConnected() + && mCallDiagnosticServiceController.onCallDisconnected(call, disconnectCause)) { + Log.i(this, "markCallAsDisconnected; callid=%s, postingToFuture.", call.getId()); + + // Log the original disconnect reason prior to calling into the + // CallDiagnosticService. + Log.addEvent(call, LogUtils.Events.SET_DISCONNECTED_ORIG, disconnectCause); + + // Setup the future with a timeout so that the CDS is time boxed. + CompletableFuture<Boolean> future = call.initializeDisconnectFuture( + mTimeoutsAdapter.getCallDiagnosticServiceTimeoutMillis( + mContext.getContentResolver())); + + // Post the disconnection updates to the future for completion once the CDS returns + // with it's overridden disconnect message. + future.thenRunAsync(() -> { + call.setDisconnectCause(disconnectCause); + setCallState(call, CallState.DISCONNECTED, "disconnected set explicitly"); + }, new LoggedHandlerExecutor(mHandler, "CM.mCAD", mLock)) + .exceptionally((throwable) -> { + Log.e(TAG, throwable, "Error while executing disconnect future."); + return null; + }); + } else { + // No CallDiagnosticService, or it doesn't handle this call, so just do this + // synchronously as always. + call.setDisconnectCause(disconnectCause); + setCallState(call, CallState.DISCONNECTED, "disconnected set explicitly"); + } + + if (oldState == CallState.NEW && disconnectCause.getCode() == DisconnectCause.MISSED) { Log.i(this, "markCallAsDisconnected: logging missed call "); mCallLogManager.logCall(call, Calls.MISSED_TYPE, true, null); } - } /** * Removes an existing disconnected call, and notifies the in-call app. */ void markCallAsRemoved(Call call) { + if (call.isDisconnectHandledViaFuture()) { + Log.i(this, "markCallAsRemoved; callid=%s, postingToFuture.", call.getId()); + // A future is being used due to a CallDiagnosticService handling the call. We will + // chain the removal operation to the end of any outstanding disconnect work. + call.getDisconnectFuture().thenRunAsync(() -> { + performRemoval(call); + }, new LoggedHandlerExecutor(mHandler, "CM.mCAR", mLock)) + .exceptionally((throwable) -> { + Log.e(TAG, throwable, "Error while executing disconnect future"); + return null; + }); + + } else { + Log.i(this, "markCallAsRemoved; callid=%s, immediate.", call.getId()); + performRemoval(call); + } + } + + /** + * Work which is completed when a call is to be removed. Can either be be run synchronously or + * posted to a {@link Call#getDisconnectFuture()}. + * @param call The call. + */ + private void performRemoval(Call call) { mInCallController.getBindingFuture().thenRunAsync(() -> { call.maybeCleanupHandover(); removeCall(call); Call foregroundCall = mCallAudioManager.getPossiblyHeldForegroundCall(); if (mLocallyDisconnectingCalls.contains(call)) { boolean isDisconnectingChildCall = call.isDisconnectingChildCall(); - Log.v(this, "markCallAsRemoved: isDisconnectingChildCall = " + Log.v(this, "performRemoval: isDisconnectingChildCall = " + isDisconnectingChildCall + "call -> %s", call); mLocallyDisconnectingCalls.remove(call); // Auto-unhold the foreground call due to a locally disconnected call, except if the @@ -3114,10 +3235,11 @@ public class CallsManager extends Call.ListenerBase // The new foreground call is on hold, however the carrier does not display the hold // button in the UI. Therefore, we need to auto unhold the held call since the user // has no means of unholding it themselves. - Log.i(this, "Auto-unholding held foreground call (call doesn't support hold)"); + Log.i(this, "performRemoval: Auto-unholding held foreground call (call doesn't " + + "support hold)"); foregroundCall.unhold(); } - }, new LoggedHandlerExecutor(mHandler, "CM.mCAR", mLock)) + }, new LoggedHandlerExecutor(mHandler, "CM.pR", mLock)) .exceptionally((throwable) -> { Log.e(TAG, throwable, "Error while executing call removal"); return null; @@ -4195,7 +4317,8 @@ public class CallsManager extends Call.ListenerBase return false; } - private boolean makeRoomForOutgoingCall(Call call) { + @VisibleForTesting + public boolean makeRoomForOutgoingCall(Call call) { // Already room! if (!hasMaximumLiveCalls(call)) return true; @@ -4212,6 +4335,13 @@ public class CallsManager extends Call.ListenerBase return true; } + // If the live call is stuck in a connecting state, then we should disconnect it in favor + // of the new outgoing call. + if (liveCall.getState() == CallState.CONNECTING) { + liveCall.disconnect("Force disconnect CONNECTING call."); + return true; + } + if (hasMaximumOutgoingCalls(call)) { Call outgoingCall = getFirstCallWithState(OUTGOING_CALL_STATES); if (outgoingCall.getState() == CallState.SELECT_PHONE_ACCOUNT) { @@ -5447,4 +5577,10 @@ public class CallsManager extends Call.ListenerBase public void addToPendingCallsToDisconnect(Call call) { mPendingCallsToDisconnect.add(call); } + + @VisibleForTesting + public void addConnectionServiceRepositoryCache(ComponentName componentName, + UserHandle userHandle, ConnectionServiceWrapper service) { + mConnectionServiceRepository.setService(componentName, userHandle, service); + } } diff --git a/src/com/android/server/telecom/CarModeTracker.java b/src/com/android/server/telecom/CarModeTracker.java index e64ef5de8..737ce5a82 100644 --- a/src/com/android/server/telecom/CarModeTracker.java +++ b/src/com/android/server/telecom/CarModeTracker.java @@ -30,6 +30,7 @@ import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.PriorityQueue; +import java.util.function.Function; import java.util.stream.Collectors; /** @@ -40,30 +41,40 @@ public class CarModeTracker { * Data class holding information about apps which have requested to enter car mode. */ private class CarModeApp { - private @IntRange(from = 0) int mPriority; + private final boolean mAutomotiveProjection; + private final @IntRange(from = 0) int mPriority; private @NonNull String mPackageName; + public CarModeApp(@NonNull String packageName) { + this(true, 0, packageName); + } + public CarModeApp(int priority, @NonNull String packageName) { + this(false, priority, packageName); + } + + private CarModeApp(boolean automotiveProjection, int priority, @NonNull String packageName) { + mAutomotiveProjection = automotiveProjection; mPriority = priority; mPackageName = Objects.requireNonNull(packageName); } + public boolean hasSetAutomotiveProjection() { + return mAutomotiveProjection; + } + /** * The priority at which the app requested to enter car mode. * Will be the same as the one specified when {@link UiModeManager#enableCarMode(int, int)} - * was called, or {@link UiModeManager#DEFAULT_PRIORITY} if no priority was specifeid. + * was called, or {@link UiModeManager#DEFAULT_PRIORITY} if no priority was specified. * @return The priority. */ public int getPriority() { return mPriority; } - public void setPriority(int priority) { - mPriority = priority; - } - /** - * @return The package name of the app which requested to enter car mode. + * @return The package name of the app which requested to enter car mode/set projection. */ public String getPackageName() { return mPackageName; @@ -72,26 +83,24 @@ public class CarModeTracker { public void setPackageName(String packageName) { mPackageName = packageName; } - } - /** - * Comparator used to maintain the car mode priority queue ordering. - */ - private class CarModeAppComparator implements Comparator<CarModeApp> { - @Override - public int compare(CarModeApp o1, CarModeApp o2) { - // highest priority takes precedence. - return Integer.compare(o2.getPriority(), o1.getPriority()); + public String toString() { + return String.format("[%s, %s]", + mAutomotiveProjection ? "PROJECTION SET" : mPriority, + mPackageName); } } /** - * Priority list of apps which have entered or exited car mode, ordered with the highest - * priority app at the top of the queue. Where items have the same priority, they are ordered - * by insertion time. + * Priority list of apps which have entered or exited car mode, ordered first by whether the app + * has set automotive projection, and then by highest priority. Where items have the same + * priority, order is arbitrary, but we only allow one item in the queue per priority. */ private PriorityQueue<CarModeApp> mCarModeApps = new PriorityQueue<>(2, - new CarModeAppComparator()); + // Natural ordering of booleans is False, True. Natural ordering of ints is increasing. + Comparator.comparing(CarModeApp::hasSetAutomotiveProjection) + .thenComparing(CarModeApp::getPriority) + .reversed()); private final LocalLog mCarModeChangeLog = new LocalLog(20); @@ -144,6 +153,47 @@ public class CarModeTracker { mCarModeApps.removeIf(c -> c.getPriority() == priority); } + public void handleSetAutomotiveProjection(@NonNull String packageName) { + Optional<CarModeApp> projectingApp = mCarModeApps.stream() + .filter(CarModeApp::hasSetAutomotiveProjection) + .findAny(); + // No app with automotive projection? Easy peasy, just add it. + if (!projectingApp.isPresent()) { + Log.i(this, "handleSetAutomotiveProjection: %s", packageName); + mCarModeChangeLog.log("setAutomotiveProjection: packageName=" + packageName); + mCarModeApps.add(new CarModeApp(packageName)); + return; + } + // Otherwise an app already has automotive projection set. Is it the same app? + if (packageName.equals(projectingApp.get().getPackageName())) { + Log.w(this, "handleSetAutomotiveProjection: %s already the automotive projection app", + packageName); + return; + } + // We have a new app for automotive projection. As a shortcut just reuse the same object by + // overwriting the package name. + Log.i(this, "handleSetAutomotiveProjection: %s replacing %s as automotive projection app", + packageName, projectingApp.get().getPackageName()); + mCarModeChangeLog.log("setAutomotiveProjection: " + packageName + " replaces " + + projectingApp.get().getPackageName()); + projectingApp.get().setPackageName(packageName); + } + + public void handleReleaseAutomotiveProjection() { + Optional<String> projectingPackage = mCarModeApps.stream() + .filter(CarModeApp::hasSetAutomotiveProjection) + .map(CarModeApp::getPackageName) + .findAny(); + if (!projectingPackage.isPresent()) { + Log.w(this, "handleReleaseAutomotiveProjection: no current automotive projection app"); + return; + } + Log.i(this, "handleReleaseAutomotiveProjection: %s", projectingPackage.get()); + mCarModeChangeLog.log("releaseAutomotiveProjection: packageName=" + + projectingPackage.get()); + mCarModeApps.removeIf(CarModeApp::hasSetAutomotiveProjection); + } + /** * Force-removes a package from the car mode tracking list, no matter at which priority. * @@ -151,19 +201,21 @@ public class CarModeTracker { * from the tracking list so they don't cause a leak. * @param packageName Package name of the app to force-remove */ - public void forceExitCarMode(@NonNull String packageName) { - Optional<CarModeApp> forcedApp = mCarModeApps.stream() + public void forceRemove(@NonNull String packageName) { + // We must account for the possibility that the app has set both car mode AND projection. + List<CarModeApp> forcedApp = mCarModeApps.stream() .filter(c -> c.getPackageName().equals(packageName)) - .findAny(); - if (forcedApp.isPresent()) { - String logString = String.format("forceExitCarMode: packageName=%s, was at priority=%s", - packageName, forcedApp.get().getPriority()); + .collect(Collectors.toList()); + if (forcedApp.isEmpty()) { + Log.i(this, "Package %s is not tracked.", packageName); + return; + } + for (CarModeApp app : forcedApp) { + String logString = "forceRemove: " + app; Log.i(this, logString); mCarModeChangeLog.log(logString); - mCarModeApps.removeIf(c -> c.getPackageName().equals(packageName)); - } else { - Log.i(this, "Package %s is not tracked as requesting car mode", packageName); } + mCarModeApps.removeIf(c -> c.getPackageName().equals(packageName)); } /** @@ -175,7 +227,7 @@ public class CarModeTracker { return mCarModeApps .stream() .sorted(mCarModeApps.comparator()) - .map(cma -> cma.getPackageName()) + .map(CarModeApp::getPackageName) .collect(Collectors.toList()); } @@ -183,7 +235,7 @@ public class CarModeTracker { return mCarModeApps .stream() .sorted(mCarModeApps.comparator()) - .map(cma -> "[" + cma.getPriority() + ", " + cma.getPackageName() + "]") + .map(CarModeApp::toString) .collect(Collectors.joining(", ")); } @@ -216,7 +268,7 @@ public class CarModeTracker { pw.increaseIndent(); for (CarModeApp app : mCarModeApps) { pw.print("["); - pw.print(app.getPriority()); + pw.print(app.hasSetAutomotiveProjection() ? "PROJECTION SET" : app.getPriority()); pw.print("] "); pw.println(app.getPackageName()); } diff --git a/src/com/android/server/telecom/ConnectionServiceFocusManager.java b/src/com/android/server/telecom/ConnectionServiceFocusManager.java index fbb23f4a8..aa0a64f69 100644 --- a/src/com/android/server/telecom/ConnectionServiceFocusManager.java +++ b/src/com/android/server/telecom/ConnectionServiceFocusManager.java @@ -391,7 +391,7 @@ public class ConnectionServiceFocusManager { } private void handleRequestFocus(FocusRequest focusRequest) { - Log.d(this, "handleRequestFocus req = %s", focusRequest); + Log.i(this, "handleRequestFocus req = %s", focusRequest); if (mCurrentFocus == null || mCurrentFocus.equals(focusRequest.call.getConnectionServiceWrapper())) { updateConnectionServiceFocus(focusRequest.call.getConnectionServiceWrapper()); diff --git a/src/com/android/server/telecom/ConnectionServiceRepository.java b/src/com/android/server/telecom/ConnectionServiceRepository.java index d34ea3c07..3991ed51f 100644 --- a/src/com/android/server/telecom/ConnectionServiceRepository.java +++ b/src/com/android/server/telecom/ConnectionServiceRepository.java @@ -79,6 +79,13 @@ public class ConnectionServiceRepository { return service; } + @VisibleForTesting + public void setService(ComponentName componentName, UserHandle userHandle, + ConnectionServiceWrapper service) { + Pair<ComponentName, UserHandle> cacheKey = Pair.create(componentName, userHandle); + mServiceCache.put(cacheKey, service); + } + /** * Dumps the state of the {@link ConnectionServiceRepository}. * diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java index 1fc9a3bd4..21c684499 100755 --- a/src/com/android/server/telecom/ConnectionServiceWrapper.java +++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java @@ -18,6 +18,7 @@ package com.android.server.telecom; import static android.Manifest.permission.MODIFY_PHONE_STATE; +import android.Manifest; import android.app.AppOpsManager; import android.content.ComponentName; import android.content.Context; @@ -30,6 +31,7 @@ import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.UserHandle; import android.telecom.CallAudioState; +import android.telecom.CallScreeningService; import android.telecom.Connection; import android.telecom.ConnectionRequest; import android.telecom.ConnectionService; @@ -350,7 +352,7 @@ public class ConnectionServiceWrapper extends ServiceBinder implements logIncoming("removeCall %s", callId); Call call = mCallIdMapper.getCall(callId); if (call != null) { - if (call.isAlive()) { + if (call.isAlive() && !call.isDisconnectHandledViaFuture()) { mCallsManager.markCallAsDisconnected( call, new DisconnectCause(DisconnectCause.REMOTE)); } else { @@ -1360,7 +1362,8 @@ public class ConnectionServiceWrapper extends ServiceBinder implements * create a connection has been denied or failed. * @param call The call. */ - void createConnectionFailed(final Call call) { + @VisibleForTesting + public void createConnectionFailed(final Call call) { Log.d(this, "createConnectionFailed(%s) via %s.", call, getComponentName()); BindCallback callback = new BindCallback() { @Override @@ -1871,6 +1874,28 @@ public class ConnectionServiceWrapper extends ServiceBinder implements } } + void onCallFilteringCompleted(Call call, + Connection.CallFilteringCompletionInfo completionInfo) { + final String callId = mCallIdMapper.getCallId(call); + if (callId != null && isServiceValid("onCallFilteringCompleted")) { + try { + logOutgoing("onCallFilteringCompleted %s", completionInfo); + int contactsPermission = mContext.getPackageManager() + .checkPermission(Manifest.permission.READ_CONTACTS, + getComponentName().getPackageName()); + if (contactsPermission == PackageManager.PERMISSION_GRANTED) { + mServiceInterface.onCallFilteringCompleted(callId, completionInfo, + Log.getExternalSession(TELECOM_ABBREVIATION)); + } else { + logOutgoing("Skipping call filtering complete message for %s due" + + " to lack of READ_CONTACTS", getComponentName().getPackageName()); + } + } catch (RemoteException e) { + Log.e(this, e, "Remote exception calling onCallFilteringCompleted"); + } + } + } + void onExtrasChanged(Call call, Bundle extras) { final String callId = mCallIdMapper.getCallId(call); if (callId != null && isServiceValid("onExtrasChanged")) { diff --git a/src/com/android/server/telecom/DtmfLocalTonePlayer.java b/src/com/android/server/telecom/DtmfLocalTonePlayer.java index 304a698d8..5869008b3 100644 --- a/src/com/android/server/telecom/DtmfLocalTonePlayer.java +++ b/src/com/android/server/telecom/DtmfLocalTonePlayer.java @@ -193,8 +193,9 @@ public class DtmfLocalTonePlayer { final Context context = call.getContext(); final boolean areLocalTonesEnabled; if (context.getResources().getBoolean(R.bool.allow_local_dtmf_tones)) { - areLocalTonesEnabled = Settings.System.getInt( - context.getContentResolver(), Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1; + areLocalTonesEnabled = Settings.System.getIntForUser( + context.getContentResolver(), Settings.System.DTMF_TONE_WHEN_DIALING, 1, + context.getUserId()) == 1; } else { areLocalTonesEnabled = false; } diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java index b138eaed6..ca76456f6 100644 --- a/src/com/android/server/telecom/InCallController.java +++ b/src/com/android/server/telecom/InCallController.java @@ -16,6 +16,7 @@ package com.android.server.telecom; +import static android.app.AppOpsManager.OPSTR_RECORD_AUDIO; import static android.os.Process.myUid; import android.Manifest; @@ -23,6 +24,7 @@ import android.annotation.NonNull; import android.app.AppOpsManager; import android.app.Notification; import android.app.NotificationManager; +import android.content.AttributionSource; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -33,13 +35,17 @@ import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.hardware.SensorPrivacyManager; +import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; +import android.os.PackageTagsList; import android.os.RemoteException; import android.os.Trace; import android.os.UserHandle; +import android.os.UserManager; import android.telecom.CallAudioState; import android.telecom.ConnectionService; import android.telecom.InCallService; @@ -54,6 +60,7 @@ import android.util.ArraySet; import com.android.internal.annotations.VisibleForTesting; // TODO: Needed for move to system service: import com.android.internal.R; import com.android.internal.telecom.IInCallService; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.server.telecom.SystemStateHelper.SystemStateListener; import com.android.server.telecom.ui.NotificationChannelManager; @@ -75,9 +82,10 @@ import java.util.stream.Collectors; * can send updates to the in-call app. This class is created and owned by CallsManager and retains * a binding to the {@link IInCallService} (implemented by the in-call app). */ -public class InCallController extends CallsManagerListenerBase { - public static final int IN_CALL_SERVICE_NOTIFICATION_ID = 3; +public class InCallController extends CallsManagerListenerBase implements + AppOpsManager.OnOpActiveChangedListener { public static final String NOTIFICATION_TAG = InCallController.class.getSimpleName(); + public static final int IN_CALL_SERVICE_NOTIFICATION_ID = 3; public class InCallServiceConnection { /** @@ -274,7 +282,16 @@ public class InCallController extends CallsManagerListenerBase { @Override public int connect(Call call) { if (mIsConnected) { - Log.addEvent(call, LogUtils.Events.INFO, "Already connected, ignoring request."); + Log.addEvent(call, LogUtils.Events.INFO, "Already connected, ignoring request: " + + mInCallServiceInfo); + if (call != null) { + // Track the call if we don't already know about it. + addCall(call); + + // Notify this new added call + sendCallToService(call, mInCallServiceInfo, + mInCallServices.get(mInCallServiceInfo)); + } return CONNECTION_SUCCEEDED; } @@ -289,7 +306,7 @@ public class InCallController extends CallsManagerListenerBase { Intent intent = new Intent(InCallService.SERVICE_INTERFACE); intent.setComponent(mInCallServiceInfo.getComponentName()); - if (call != null && !call.isIncoming() && !call.isExternalCall()){ + if (call != null && !call.isIncoming() && !call.isExternalCall()) { intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, call.getIntentExtras()); intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, @@ -300,9 +317,9 @@ public class InCallController extends CallsManagerListenerBase { mIsConnected = true; mInCallServiceInfo.setBindingStartTime(mClockProxy.elapsedRealtime()); if (!mContext.bindServiceAsUser(intent, mServiceConnection, - Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE - | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS, - UserHandle.CURRENT)) { + Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE + | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS, + UserHandle.CURRENT)) { Log.w(this, "Failed to connect."); mIsConnected = false; } @@ -442,15 +459,15 @@ public class InCallController extends CallsManagerListenerBase { } mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(call, - mCallsManager.getCurrentUserHandle()); + mCallsManager.getCurrentUserHandle()); if (call != null && call.isIncoming() - && mEmergencyCallHelper.getLastEmergencyCallTimeMillis() > 0) { - // Add the last emergency call time to the call - Bundle extras = new Bundle(); - extras.putLong(android.telecom.Call.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS, - mEmergencyCallHelper.getLastEmergencyCallTimeMillis()); - call.putExtras(Call.SOURCE_CONNECTION_SERVICE, extras); + && mEmergencyCallHelper.getLastEmergencyCallTimeMillis() > 0) { + // Add the last emergency call time to the call + Bundle extras = new Bundle(); + extras.putLong(android.telecom.Call.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS, + mEmergencyCallHelper.getLastEmergencyCallTimeMillis()); + call.putExtras(Call.SOURCE_CONNECTION_SERVICE, extras); } // If we are here, we didn't or could not connect to child. So lets connect ourselves. @@ -484,6 +501,7 @@ public class InCallController extends CallsManagerListenerBase { return super.getInfo(); } } + @Override protected void onDisconnected() { // Save this here because super.onDisconnected() could force us to explicitly @@ -547,6 +565,7 @@ public class InCallController extends CallsManagerListenerBase { /** * Called when we move to a state where calls are present on the device. Chooses the * {@link InCallService} to which we should connect. + * * @param isCarMode {@code true} if device is in car mode, {@code false} otherwise. */ public synchronized void chooseInitialInCallService(boolean isCarMode) { @@ -585,6 +604,7 @@ public class InCallController extends CallsManagerListenerBase { /** * Changes the active {@link InCallService} to a car mode app. Called whenever the device * changes to car mode or the currently active car mode app changes. + * * @param packageName The package name of the car mode app. */ public synchronized void changeCarModeApp(String packageName) { @@ -593,7 +613,8 @@ public class InCallController extends CallsManagerListenerBase { InCallServiceInfo currentConnectionInfo = mCurrentConnection == null ? null : mCurrentConnection.getInfo(); InCallServiceInfo carModeConnectionInfo = - getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_CAR_MODE_UI); + getInCallServiceComponent(packageName, + IN_CALL_SERVICE_TYPE_CAR_MODE_UI, true /* ignoreDisabed */); if (!Objects.equals(currentConnectionInfo, carModeConnectionInfo)) { Log.i(this, "changeCarModeApp: " + currentConnectionInfo + " => " @@ -608,7 +629,7 @@ public class InCallController extends CallsManagerListenerBase { new InCallServiceBindingConnection(carModeConnectionInfo); mIsCarMode = true; } else { - // Invalid car mode app; don't expect this but should handle it gracefully. + // The app is not enabled. Using the default dialer connection instead mCarModeConnection = null; mIsCarMode = false; mCurrentConnection = mDialerConnection; @@ -622,6 +643,10 @@ public class InCallController extends CallsManagerListenerBase { } } + public boolean isCarMode() { + return mIsCarMode; + } + @Override public int connect(Call call) { if (mIsConnected) { @@ -878,6 +903,12 @@ public class InCallController extends CallsManagerListenerBase { public void onRemoteRttRequest(Call call, int requestId) { notifyRemoteRttRequest(call, requestId); } + + @Override + public void onCallerNumberVerificationStatusChanged(Call call, + int callerNumberVerificationStatus) { + updateCall(call); + } }; private BroadcastReceiver mPackageChangedReceiver = new BroadcastReceiver() { @@ -904,6 +935,10 @@ public class InCallController extends CallsManagerListenerBase { if (mNonUIInCallServiceConnections != null) { mNonUIInCallServiceConnections.addConnections(componentsToBind); } + + // If the current car mode app become enabled from disabled, update + // the connection to binding + updateCarModeForConnections(); } } } finally { @@ -919,8 +954,18 @@ public class InCallController extends CallsManagerListenerBase { } @Override + public void onAutomotiveProjectionStateSet(String automotiveProjectionPackage) { + InCallController.this.handleSetAutomotiveProjection(automotiveProjectionPackage); + } + + @Override + public void onAutomotiveProjectionStateReleased() { + InCallController.this.handleReleaseAutomotiveProjection(); + } + + @Override public void onPackageUninstalled(String packageName) { - mCarModeTracker.forceExitCarMode(packageName); + mCarModeTracker.forceRemove(packageName); updateCarModeForConnections(); } }; @@ -932,6 +977,9 @@ public class InCallController extends CallsManagerListenerBase { private static final int IN_CALL_SERVICE_TYPE_NON_UI = 4; private static final int IN_CALL_SERVICE_TYPE_COMPANION = 5; + private static final int[] LIVE_CALL_STATES = { CallState.ACTIVE, CallState.PULLING, + CallState.DISCONNECTING }; + /** The in-call app implementations, see {@link IInCallService}. */ private final Map<InCallServiceInfo, IInCallService> mInCallServices = new ArrayMap<>(); @@ -939,6 +987,7 @@ public class InCallController extends CallsManagerListenerBase { private final Context mContext; private final AppOpsManager mAppOpsManager; + private final SensorPrivacyManager mSensorPrivacyManager; private final TelecomSystem.SyncRoot mLock; private final CallsManager mCallsManager; private final SystemStateHelper mSystemStateHelper; @@ -949,6 +998,7 @@ public class InCallController extends CallsManagerListenerBase { private CarSwappingInCallServiceConnection mInCallServiceConnection; private NonUIInCallServiceConnectionCollection mNonUIInCallServiceConnections; private final ClockProxy mClockProxy; + private final IBinder mToken = new Binder(); // A set of known non-UI in call services on the device, including those that are disabled. // We track this so that we can efficiently bind to them when we're notified that a new @@ -968,16 +1018,33 @@ public class InCallController extends CallsManagerListenerBase { /** * {@code true} if InCallController is tracking a managed, not external call which is using the - * microphone, {@code false} otherwise. + * microphone, and is not muted {@code false} otherwise. */ private boolean mIsCallUsingMicrophone = false; + /** + * {@code true} if InCallController is tracking a managed, not external call which is using the + * microphone, {@code false} otherwise. + */ + private boolean mIsTrackingManagedAliveCall = false; + + private boolean mIsStartCallDelayScheduled = false; + + /** + * A list of call IDs which are currently using the camera. + */ + private ArrayList<String> mCallsUsingCamera = new ArrayList<>(); + + private ArraySet<String> mAllCarrierPrivilegedApps = new ArraySet<>(); + private ArraySet<String> mActiveCarrierPrivilegedApps = new ArraySet<>(); + public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager, SystemStateHelper systemStateHelper, DefaultDialerCache defaultDialerCache, Timeouts.Adapter timeoutsAdapter, EmergencyCallHelper emergencyCallHelper, CarModeTracker carModeTracker, ClockProxy clockProxy) { mContext = context; mAppOpsManager = context.getSystemService(AppOpsManager.class); + mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); mLock = lock; mCallsManager = callsManager; mSystemStateHelper = systemStateHelper; @@ -987,6 +1054,76 @@ public class InCallController extends CallsManagerListenerBase { mCarModeTracker = carModeTracker; mSystemStateHelper.addListener(mSystemStateListener); mClockProxy = clockProxy; + restrictPhoneCallOps(); + } + + private void restrictPhoneCallOps() { + PackageTagsList packageRestriction = new PackageTagsList.Builder() + .add(mContext.getPackageName()) + .build(); + mAppOpsManager.setUserRestrictionForUser(AppOpsManager.OP_PHONE_CALL_MICROPHONE, true, + mToken, packageRestriction, UserHandle.USER_ALL); + mAppOpsManager.setUserRestrictionForUser(AppOpsManager.OP_PHONE_CALL_CAMERA, true, + mToken, packageRestriction, UserHandle.USER_ALL); + } + + @Override + public void onOpActiveChanged(@androidx.annotation.NonNull String op, int uid, + @androidx.annotation.NonNull String packageName, boolean active) { + synchronized (mLock) { + if (!mAllCarrierPrivilegedApps.contains(packageName)) { + return; + } + + if (active) { + mActiveCarrierPrivilegedApps.add(packageName); + } else { + mActiveCarrierPrivilegedApps.remove(packageName); + } + maybeTrackMicrophoneUse(isMuted()); + } + } + + private void updateAllCarrierPrivilegedUsingMic() { + mActiveCarrierPrivilegedApps.clear(); + UserManager userManager = mContext.getSystemService(UserManager.class); + PackageManager pkgManager = mContext.getPackageManager(); + for (String pkg : mAllCarrierPrivilegedApps) { + boolean isActive = mActiveCarrierPrivilegedApps.contains(pkg); + List<UserHandle> users = userManager.getUserHandles(true); + for (UserHandle user : users) { + if (isActive) { + break; + } + + int uid; + try { + uid = pkgManager.getPackageUidAsUser(pkg, user.getIdentifier()); + } catch (PackageManager.NameNotFoundException e) { + continue; + } + List<AppOpsManager.PackageOps> pkgOps = mAppOpsManager.getOpsForPackage( + uid, pkg, OPSTR_RECORD_AUDIO); + for (int j = 0; j < pkgOps.size(); j++) { + List<AppOpsManager.OpEntry> opEntries = pkgOps.get(j).getOps(); + for (int k = 0; k < opEntries.size(); k++) { + AppOpsManager.OpEntry entry = opEntries.get(k); + if (entry.isRunning()) { + mActiveCarrierPrivilegedApps.add(pkg); + break; + } + } + } + } + } + } + + private void updateAllCarrierPrivileged() { + mAllCarrierPrivilegedApps.clear(); + for (Call call : mCallIdMapper.getCalls()) { + mAllCarrierPrivilegedApps.add(call.getConnectionManagerPhoneAccount() + .getComponentName().getPackageName()); + } } @Override @@ -1036,7 +1173,7 @@ public class InCallController extends CallsManagerListenerBase { true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar(), info.isExternalCallsSupported(), includeRttCall, info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI || - info.getType() == IN_CALL_SERVICE_TYPE_NON_UI); + info.getType() == IN_CALL_SERVICE_TYPE_NON_UI); try { inCallService.addCall(sanitizeParcelableCallForService(info, parcelableCall)); updateCallTracking(call, info, true /* isAdd */); @@ -1065,11 +1202,16 @@ public class InCallController extends CallsManagerListenerBase { } } }.prepare(), mTimeoutsAdapter.getCallRemoveUnbindInCallServicesDelay( - mContext.getContentResolver())); + mContext.getContentResolver())); } call.removeListener(mCallListener); mCallIdMapper.removeCall(call); + if (mCallIdMapper.getCalls().isEmpty()) { + mActiveCarrierPrivilegedApps.clear(); + mAppOpsManager.stopWatchingActive(this); + } maybeTrackMicrophoneUse(isMuted()); + onSetCamera(call, null); } @Override @@ -1103,8 +1245,8 @@ public class InCallController extends CallsManagerListenerBase { ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call, true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar(), info.isExternalCallsSupported(), includeRttCall, - info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI || - info.getType() == IN_CALL_SERVICE_TYPE_NON_UI); + info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI + || info.getType() == IN_CALL_SERVICE_TYPE_NON_UI); try { inCallService.addCall(sanitizeParcelableCallForService(info, parcelableCall)); updateCallTracking(call, info, true /* isAdd */); @@ -1135,9 +1277,9 @@ public class InCallController extends CallsManagerListenerBase { false /* supportsExternalCalls */, android.telecom.Call.STATE_DISCONNECTED /* overrideState */, false /* includeRttCall */, - info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI || - info.getType() == IN_CALL_SERVICE_TYPE_NON_UI - ); + info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI + || info.getType() == IN_CALL_SERVICE_TYPE_NON_UI + ); try { inCallService.updateCall( @@ -1221,6 +1363,7 @@ public class InCallController extends CallsManagerListenerBase { public void onIsVoipAudioModeChanged(Call call) { Log.d(this, "onIsVoipAudioModeChanged %s", call); updateCall(call); + maybeTrackMicrophoneUse(isMuted()); } @Override @@ -1237,18 +1380,35 @@ public class InCallController extends CallsManagerListenerBase { /** * Track changes to camera usage for a call. - * @param call The call. + * + * @param call The call. * @param cameraId The id of the camera to use, or {@code null} if camera is off. */ @Override public void onSetCamera(Call call, String cameraId) { + if (call == null) { + return; + } + Log.i(this, "onSetCamera callId=%s, cameraId=%s", call.getId(), cameraId); if (cameraId != null) { - mAppOpsManager.startOp(AppOpsManager.OP_PHONE_CALL_CAMERA, myUid(), - mContext.getOpPackageName(), false, null, null); + boolean shouldStart = mCallsUsingCamera.isEmpty(); + if (!mCallsUsingCamera.contains(call.getId())) { + mCallsUsingCamera.add(call.getId()); + } + + if (shouldStart) { + mAppOpsManager.startOp(AppOpsManager.OP_PHONE_CALL_CAMERA, myUid(), + mContext.getOpPackageName(), false, null, null); + mSensorPrivacyManager.showSensorUseDialog(SensorPrivacyManager.Sensors.CAMERA); + } } else { - mAppOpsManager.finishOp(AppOpsManager.OP_PHONE_CALL_CAMERA, myUid(), - mContext.getOpPackageName(), null); + boolean hadCall = !mCallsUsingCamera.isEmpty(); + mCallsUsingCamera.remove(call.getId()); + if (hadCall && mCallsUsingCamera.isEmpty()) { + mAppOpsManager.finishOp(AppOpsManager.OP_PHONE_CALL_CAMERA, myUid(), + mContext.getOpPackageName(), null); + } } } @@ -1281,8 +1441,8 @@ public class InCallController extends CallsManagerListenerBase { for (IInCallService inCallService : mInCallServices.values()) { try { Log.i(this, "notifyConnectionEvent {Call: %s, Event: %s, Extras:[%s]}", - (call != null ? call.toString() :"null"), - (event != null ? event : "null") , + (call != null ? call.toString() : "null"), + (event != null ? event : "null"), (extras != null ? extras.toString() : "null")); inCallService.onConnectionEvent(mCallIdMapper.getCallId(call), event, extras); } catch (RemoteException ignored) { @@ -1293,7 +1453,7 @@ public class InCallController extends CallsManagerListenerBase { private void notifyRttInitiationFailure(Call call, int reason) { if (!mInCallServices.isEmpty()) { - mInCallServices.entrySet().stream() + mInCallServices.entrySet().stream() .filter((entry) -> entry.getKey().equals(mInCallServiceConnection.getInfo())) .forEach((entry) -> { try { @@ -1420,6 +1580,10 @@ public class InCallController extends CallsManagerListenerBase { } else { Log.i(this, "bindToServices: current UI doesn't support call; not binding."); } + + IntentFilter packageChangedFilter = new IntentFilter(Intent.ACTION_PACKAGE_CHANGED); + packageChangedFilter.addDataScheme("package"); + mContext.registerReceiver(mPackageChangedReceiver, packageChangedFilter); } private void updateNonUiInCallServices() { @@ -1434,7 +1598,7 @@ public class InCallController extends CallsManagerListenerBase { if (callCompanionApps != null && !callCompanionApps.isEmpty()) { for (String pkg : callCompanionApps) { InCallServiceInfo info = getInCallServiceComponent(pkg, - IN_CALL_SERVICE_TYPE_COMPANION); + IN_CALL_SERVICE_TYPE_COMPANION, true /* ignoreDisabled */); if (info != null) { nonUIInCalls.add(new InCallServiceBindingConnection(info)); } @@ -1449,10 +1613,6 @@ public class InCallController extends CallsManagerListenerBase { updateNonUiInCallServices(); } mNonUIInCallServiceConnections.connect(call); - - IntentFilter packageChangedFilter = new IntentFilter(Intent.ACTION_PACKAGE_CHANGED); - packageChangedFilter.addDataScheme("package"); - mContext.registerReceiver(mPackageChangedReceiver, packageChangedFilter); } private InCallServiceInfo getDefaultDialerComponent() { @@ -1463,8 +1623,10 @@ public class InCallController extends CallsManagerListenerBase { InCallServiceInfo defaultDialerComponent = (systemPackageName != null && systemPackageName.equals(packageName)) - ? getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_SYSTEM_UI) - : getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_DEFAULT_DIALER_UI); + ? getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_SYSTEM_UI, + true /* ignoreDisabled */) + : getInCallServiceComponent(packageName, + IN_CALL_SERVICE_TYPE_DEFAULT_DIALER_UI, true /* ignoreDisabled */); /* TODO: in Android 12 re-enable this an InCallService is required by the dialer role. if (packageName != null && defaultDialerComponent == null) { // The in call service of default phone app is disabled, send notification. @@ -1476,7 +1638,7 @@ public class InCallController extends CallsManagerListenerBase { private InCallServiceInfo getCurrentCarModeComponent() { return getInCallServiceComponent(mCarModeTracker.getCurrentCarModePackage(), - IN_CALL_SERVICE_TYPE_CAR_MODE_UI); + IN_CALL_SERVICE_TYPE_CAR_MODE_UI, true /* ignoreDisabled */); } private InCallServiceInfo getInCallServiceComponent(ComponentName componentName, int type) { @@ -1486,13 +1648,15 @@ public class InCallController extends CallsManagerListenerBase { } else { // Last Resort: Try to bind to the ComponentName given directly. Log.e(this, new Exception(), "Package Manager could not find ComponentName: " - + componentName +". Trying to bind anyway."); + + componentName + ". Trying to bind anyway."); return new InCallServiceInfo(componentName, false, false, type); } } - private InCallServiceInfo getInCallServiceComponent(String packageName, int type) { - List<InCallServiceInfo> list = getInCallServiceComponents(packageName, type); + private InCallServiceInfo getInCallServiceComponent(String packageName, int type, + boolean ignoreDisabled) { + List<InCallServiceInfo> list = getInCallServiceComponents(packageName, type, + ignoreDisabled); if (list != null && !list.isEmpty()) { return list.get(0); } @@ -1503,8 +1667,9 @@ public class InCallController extends CallsManagerListenerBase { return getInCallServiceComponents(null, null, type); } - private List<InCallServiceInfo> getInCallServiceComponents(String packageName, int type) { - return getInCallServiceComponents(packageName, null, type); + private List<InCallServiceInfo> getInCallServiceComponents(String packageName, int type, + boolean ignoreDisabled) { + return getInCallServiceComponents(packageName, null, type, ignoreDisabled); } private List<InCallServiceInfo> getInCallServiceComponents(ComponentName componentName, @@ -1514,6 +1679,12 @@ public class InCallController extends CallsManagerListenerBase { private List<InCallServiceInfo> getInCallServiceComponents(String packageName, ComponentName componentName, int requestedType) { + return getInCallServiceComponents(packageName, componentName, requestedType, + true /* ignoreDisabled */); + } + + private List<InCallServiceInfo> getInCallServiceComponents(String packageName, + ComponentName componentName, int requestedType, boolean ignoreDisabled) { List<InCallServiceInfo> retval = new LinkedList<>(); Intent serviceIntent = new Intent(InCallService.SERVICE_INTERFACE); @@ -1546,13 +1717,16 @@ public class InCallController extends CallsManagerListenerBase { mKnownNonUiInCallServices.add(foundComponentName); } + boolean isEnabled = isServiceEnabled(foundComponentName, + serviceInfo, packageManager); boolean isRequestedType; if (requestedType == IN_CALL_SERVICE_TYPE_INVALID) { isRequestedType = true; } else { isRequestedType = requestedType == currentType; } - if (serviceInfo.enabled && isRequestedType) { + + if ((!ignoreDisabled || isEnabled) && isRequestedType) { retval.add(new InCallServiceInfo(foundComponentName, isExternalCallsSupported, isSelfManageCallsSupported, requestedType)); } @@ -1561,6 +1735,21 @@ public class InCallController extends CallsManagerListenerBase { return retval; } + private boolean isServiceEnabled(ComponentName componentName, + ServiceInfo serviceInfo, PackageManager packageManager) { + int componentEnabledState = packageManager.getComponentEnabledSetting(componentName); + + if (componentEnabledState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { + return true; + } + + if (componentEnabledState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) { + return serviceInfo.isEnabled(); + } + + return false; + } + private boolean shouldUseCarModeUI() { return mCarModeTracker.isInCarMode(); } @@ -1687,34 +1876,7 @@ public class InCallController extends CallsManagerListenerBase { "calls", calls.size(), info.getComponentName()); int numCallsSent = 0; for (Call call : calls) { - try { - if ((call.isSelfManaged() && (!info.isSelfManagedCallsSupported() - || !call.visibleToInCallService())) || - (call.isExternalCall() && !info.isExternalCallsSupported())) { - continue; - } - - // Only send the RTT call if it's a UI in-call service - boolean includeRttCall = false; - if (mInCallServiceConnection != null) { - includeRttCall = info.equals(mInCallServiceConnection.getInfo()); - } - - // Track the call if we don't already know about it. - addCall(call); - numCallsSent += 1; - ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall( - call, - true /* includeVideoProvider */, - mCallsManager.getPhoneAccountRegistrar(), - info.isExternalCallsSupported(), - includeRttCall, - info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI || - info.getType() == IN_CALL_SERVICE_TYPE_NON_UI); - inCallService.addCall(sanitizeParcelableCallForService(info, parcelableCall)); - updateCallTracking(call, info, true /* isAdd */); - } catch (RemoteException ignored) { - } + numCallsSent += sendCallToService(call, info, inCallService); } try { inCallService.onCallAudioStateChanged(mCallsManager.getAudioState()); @@ -1722,7 +1884,7 @@ public class InCallController extends CallsManagerListenerBase { } catch (RemoteException ignored) { } // Don't complete the binding future for non-ui incalls - if (info.getType() != IN_CALL_SERVICE_TYPE_NON_UI) { + if (info.getType() != IN_CALL_SERVICE_TYPE_NON_UI && !mBindingFuture.isDone()) { mBindingFuture.complete(true); } @@ -1730,6 +1892,39 @@ public class InCallController extends CallsManagerListenerBase { return true; } + private int sendCallToService(Call call, InCallServiceInfo info, + IInCallService inCallService) { + try { + if ((call.isSelfManaged() && (!info.isSelfManagedCallsSupported() + || !call.visibleToInCallService())) || + (call.isExternalCall() && !info.isExternalCallsSupported())) { + return 0; + } + + // Only send the RTT call if it's a UI in-call service + boolean includeRttCall = false; + if (mInCallServiceConnection != null) { + includeRttCall = info.equals(mInCallServiceConnection.getInfo()); + } + + // Track the call if we don't already know about it. + addCall(call); + ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall( + call, + true /* includeVideoProvider */, + mCallsManager.getPhoneAccountRegistrar(), + info.isExternalCallsSupported(), + includeRttCall, + info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI || + info.getType() == IN_CALL_SERVICE_TYPE_NON_UI); + inCallService.addCall(sanitizeParcelableCallForService(info, parcelableCall)); + updateCallTracking(call, info, true /* isAdd */); + return 1; + } catch (RemoteException ignored) { + } + return 0; + } + /** * Cleans up an instance of in-call app after the service has been unbound. * @@ -1805,10 +2000,18 @@ public class InCallController extends CallsManagerListenerBase { * @param call The call to add. */ private void addCall(Call call) { + if (mCallIdMapper.getCalls().size() == 0) { + mAppOpsManager.startWatchingActive(new String[] { OPSTR_RECORD_AUDIO }, + java.lang.Runnable::run, this); + updateAllCarrierPrivileged(); + updateAllCarrierPrivilegedUsingMic(); + } + if (mCallIdMapper.getCallId(call) == null) { mCallIdMapper.addCall(call); call.addListener(mCallListener); } + maybeTrackMicrophoneUse(isMuted()); } @@ -1952,8 +2155,11 @@ public class InCallController extends CallsManagerListenerBase { * {@code false} otherwise. */ private boolean isCarModeInCallService(@NonNull String packageName) { + // Disabled InCallService should also be considered as a valid InCallService here so that + // it can be added to the CarModeTracker, in case it will be enabled in future. InCallServiceInfo info = - getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_CAR_MODE_UI); + getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_CAR_MODE_UI, + false /* ignoreDisabled */); return info != null && info.getType() == IN_CALL_SERVICE_TYPE_CAR_MODE_UI; } @@ -1976,15 +2182,39 @@ public class InCallController extends CallsManagerListenerBase { updateCarModeForConnections(); } + public void handleSetAutomotiveProjection(@NonNull String packageName) { + Log.i(this, "handleSetAutomotiveProjection: packageName=%s", packageName); + if (!isCarModeInCallService(packageName)) { + Log.i(this, "handleSetAutomotiveProjection: not a valid InCallService: packageName=%s", + packageName); + return; + } + mCarModeTracker.handleSetAutomotiveProjection(packageName); + + updateCarModeForConnections(); + } + + public void handleReleaseAutomotiveProjection() { + Log.i(this, "handleReleaseAutomotiveProjection"); + mCarModeTracker.handleReleaseAutomotiveProjection(); + + updateCarModeForConnections(); + } + public void updateCarModeForConnections() { Log.i(this, "updateCarModeForConnections: car mode apps: %s", mCarModeTracker.getCarModeApps().stream().collect(Collectors.joining(", "))); if (mInCallServiceConnection != null) { if (shouldUseCarModeUI()) { + Log.i(this, "updateCarModeForConnections: potentially update car mode app."); mInCallServiceConnection.changeCarModeApp( mCarModeTracker.getCurrentCarModePackage()); } else { - mInCallServiceConnection.disableCarMode(); + if (mInCallServiceConnection.isCarMode()) { + Log.i(this, "updateCarModeForConnections: car mode no longer " + + "applicable; disabling"); + mInCallServiceConnection.disableCarMode(); + } } } } @@ -2013,18 +2243,42 @@ public class InCallController extends CallsManagerListenerBase { Log.i(this, "trackCallingUserInterfaceStopped: %s is no longer calling UX", packageName); } + private void maybeTrackMicrophoneUse(boolean isMuted) { + maybeTrackMicrophoneUse(isMuted, false); + } + /** * As calls are added, removed and change between external and non-external status, track * whether the current active calling UX is using the microphone. We assume if there is a * managed call present and the mic is not muted that the microphone is in use. */ - private void maybeTrackMicrophoneUse(boolean isMuted) { - boolean wasTrackingManagedCall = mIsCallUsingMicrophone; - mIsCallUsingMicrophone = isTrackingManagedAliveCall() && !isMuted; - if (wasTrackingManagedCall != mIsCallUsingMicrophone) { + private void maybeTrackMicrophoneUse(boolean isMuted, boolean isScheduledDelay) { + if (mIsStartCallDelayScheduled && !isScheduledDelay) { + return; + } + + mIsStartCallDelayScheduled = false; + boolean wasUsingMicrophone = mIsCallUsingMicrophone; + boolean wasTrackingCall = mIsTrackingManagedAliveCall; + mIsTrackingManagedAliveCall = isTrackingManagedAliveCall(); + if (!wasTrackingCall && mIsTrackingManagedAliveCall) { + mIsStartCallDelayScheduled = true; + mHandler.postDelayed(new Runnable("ICC.mTMU", mLock) { + @Override + public void loggedRun() { + maybeTrackMicrophoneUse(isMuted(), true); + } + }.prepare(), mTimeoutsAdapter.getCallStartAppOpDebounceIntervalMillis()); + return; + } + + mIsCallUsingMicrophone = mIsTrackingManagedAliveCall && !isMuted + && !isCarrierPrivilegedUsingMicDuringVoipCall(); + if (wasUsingMicrophone != mIsCallUsingMicrophone) { if (mIsCallUsingMicrophone) { mAppOpsManager.startOp(AppOpsManager.OP_PHONE_CALL_MICROPHONE, myUid(), mContext.getOpPackageName(), false, null, null); + mSensorPrivacyManager.showSensorUseDialog(SensorPrivacyManager.Sensors.MICROPHONE); } else { mAppOpsManager.finishOp(AppOpsManager.OP_PHONE_CALL_MICROPHONE, myUid(), mContext.getOpPackageName(), null); @@ -2038,8 +2292,13 @@ public class InCallController extends CallsManagerListenerBase { */ private boolean isTrackingManagedAliveCall() { return mCallIdMapper.getCalls().stream().anyMatch(c -> !c.isExternalCall() - && !c.isSelfManaged() && c.isAlive() && c.getState() != CallState.ON_HOLD - && c.getState() != CallState.AUDIO_PROCESSING); + && !c.isSelfManaged() && c.isAlive() && ArrayUtils.contains(LIVE_CALL_STATES, + c.getState())); + } + + private boolean isCarrierPrivilegedUsingMicDuringVoipCall() { + return !mActiveCarrierPrivilegedApps.isEmpty() && + mCallIdMapper.getCalls().stream().anyMatch(Call::getIsVoipAudioMode); } /** @@ -2053,9 +2312,13 @@ public class InCallController extends CallsManagerListenerBase { } private boolean isAppOpsPermittedManageOngoingCalls(int uid, String callingPackage) { - return PermissionChecker.checkPermissionForPreflight(mContext, - Manifest.permission.MANAGE_ONGOING_CALLS, PermissionChecker.PID_UNKNOWN, uid, - callingPackage) == PermissionChecker.PERMISSION_GRANTED; + return PermissionChecker.checkPermissionForDataDeliveryFromDataSource(mContext, + Manifest.permission.MANAGE_ONGOING_CALLS, PermissionChecker.PID_UNKNOWN, + new AttributionSource(mContext.getAttributionSource(), + new AttributionSource(uid, callingPackage, + /*attributionTag*/ null)), "Checking whether the app has" + + " MANAGE_ONGOING_CALLS permission") + == PermissionChecker.PERMISSION_GRANTED; } private void sendCrashedInCallServiceNotification(String packageName) { diff --git a/src/com/android/server/telecom/LogUtils.java b/src/com/android/server/telecom/LogUtils.java index a9bf18ce0..138e4414b 100644 --- a/src/com/android/server/telecom/LogUtils.java +++ b/src/com/android/server/telecom/LogUtils.java @@ -197,6 +197,11 @@ public class LogUtils { 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 final String SET_DISCONNECTED_ORIG = "SET_DISCONNECTED_ORIG"; + public static final String OVERRIDE_DISCONNECT_MESSAGE = "OVERRIDE_DISCONNECT_MSG"; + public static final String CALL_DIAGNOSTIC_SERVICE_TIMEOUT = + "CALL_DIAGNOSTIC_SERVICE_TIMEOUT"; + public static final String VERSTAT_CHANGED = "VERSTAT_CHANGED"; public static class Timings { public static final String ACCEPT_TIMING = "accept"; diff --git a/src/com/android/server/telecom/ParcelableCallUtils.java b/src/com/android/server/telecom/ParcelableCallUtils.java index 8c46e10d6..0becaef30 100644 --- a/src/com/android/server/telecom/ParcelableCallUtils.java +++ b/src/com/android/server/telecom/ParcelableCallUtils.java @@ -27,6 +27,7 @@ import android.telecom.DisconnectCause; import android.telecom.ParcelableCall; import android.telecom.ParcelableRttCall; import android.telecom.TelecomManager; +import android.telephony.ims.ImsCallProfile; import android.text.TextUtils; import java.util.ArrayList; @@ -61,6 +62,7 @@ public class ParcelableCallUtils { static { RESTRICTED_CALL_SCREENING_EXTRA_KEYS = new ArrayList<>(); RESTRICTED_CALL_SCREENING_EXTRA_KEYS.add(android.telecom.Connection.EXTRA_SIP_INVITE); + RESTRICTED_CALL_SCREENING_EXTRA_KEYS.add(ImsCallProfile.EXTRA_IS_BUSINESS_CALL); } public static class Converter { @@ -558,7 +560,10 @@ public class ParcelableCallUtils { android.telecom.Call.Details.PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL, Connection.PROPERTY_IS_ADHOC_CONFERENCE, - android.telecom.Call.Details.PROPERTY_IS_ADHOC_CONFERENCE + android.telecom.Call.Details.PROPERTY_IS_ADHOC_CONFERENCE, + + Connection.PROPERTY_CROSS_SIM, + android.telecom.Call.Details.PROPERTY_CROSS_SIM }; private static int convertConnectionToCallProperties(int connectionProperties) { diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java index 02f0dabfa..bce6e4302 100644 --- a/src/com/android/server/telecom/PhoneAccountRegistrar.java +++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java @@ -2001,8 +2001,8 @@ public class PhoneAccountRegistrar { * @return {@code True} if SIP should be used for all calls. */ private boolean useSipForPstnCalls(Context context) { - String option = Settings.System.getString(context.getContentResolver(), - Settings.System.SIP_CALL_OPTIONS); + String option = Settings.System.getStringForUser(context.getContentResolver(), + Settings.System.SIP_CALL_OPTIONS, context.getUserId()); option = (option != null) ? option : Settings.System.SIP_ADDRESS_ONLY; return option.equals(Settings.System.SIP_ALWAYS); } diff --git a/src/com/android/server/telecom/PhoneStateBroadcaster.java b/src/com/android/server/telecom/PhoneStateBroadcaster.java index f02f7e8ac..490db8547 100644 --- a/src/com/android/server/telecom/PhoneStateBroadcaster.java +++ b/src/com/android/server/telecom/PhoneStateBroadcaster.java @@ -130,10 +130,17 @@ final class PhoneStateBroadcaster extends CallsManagerListenerBase { TelephonyManager tm = mCallsManager.getContext().getSystemService(TelephonyManager.class); String strippedNumber = PhoneNumberUtils.stripSeparators(call.getHandle().getSchemeSpecificPart()); - Optional<EmergencyNumber> emergencyNumber = tm.getEmergencyNumberList().values().stream() - .flatMap(List::stream) - .filter(numberObj -> Objects.equals(numberObj.getNumber(), strippedNumber)) - .findFirst(); + Optional<EmergencyNumber> emergencyNumber; + try { + emergencyNumber = tm.getEmergencyNumberList().values().stream() + .flatMap(List::stream) + .filter(numberObj -> Objects.equals(numberObj.getNumber(), strippedNumber)) + .findFirst(); + } catch (IllegalStateException ie) { + emergencyNumber = Optional.empty(); + } catch (RuntimeException r) { + emergencyNumber = Optional.empty(); + } int subscriptionId = tm.getSubscriptionId(call.getTargetPhoneAccount()); SubscriptionManager subscriptionManager = diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java index a769a9496..91276ded8 100644 --- a/src/com/android/server/telecom/Ringer.java +++ b/src/com/android/server/telecom/Ringer.java @@ -20,26 +20,27 @@ import android.app.Notification; import android.app.NotificationManager; import android.app.Person; import android.content.Context; -import android.os.VibrationEffect; -import android.telecom.Log; -import android.telecom.TelecomManager; import android.media.AudioAttributes; import android.media.AudioManager; import android.media.Ringtone; import android.media.VolumeShaper; import android.net.Uri; import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.VibrationEffect; import android.os.Vibrator; +import android.telecom.Log; +import android.telecom.TelecomManager; import com.android.internal.annotations.VisibleForTesting; import com.android.server.telecom.LogUtils.EventTimer; import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -import java.util.stream.Collectors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; /** * Controls the ringtone player. @@ -114,6 +115,8 @@ public class Ringer { private static final int REPEAT_SIMPLE_VIBRATION_AT = 1; + private static final long RINGER_ATTRIBUTES_TIMEOUT = 5000; // 5 seconds + private static final float EPSILON = 1e-6f; private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder() @@ -147,6 +150,7 @@ public class Ringer { private InCallTonePlayer mCallWaitingPlayer; private RingtoneFactory mRingtoneFactory; + private AudioManager mAudioManager; /** * Call objects that are ringing, vibrating or call-waiting. These are used only for logging @@ -161,6 +165,8 @@ public class Ringer { */ private boolean mIsVibrating = false; + private Handler mHandler = null; + /** Initializes the Ringer. */ @VisibleForTesting public Ringer( @@ -183,6 +189,7 @@ public class Ringer { mRingtoneFactory = ringtoneFactory; mInCallController = inCallController; mVibrationEffectProxy = vibrationEffectProxy; + mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); if (mContext.getResources().getBoolean(R.bool.use_simple_vibration_pattern)) { mDefaultVibrationEffect = mVibrationEffectProxy.createWaveform(SIMPLE_VIBRATION_PATTERN, @@ -216,58 +223,39 @@ public class Ringer { return false; } - AudioManager audioManager = - (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); - LogUtils.EventTimer timer = new EventTimer(); - boolean isVolumeOverZero = audioManager.getStreamVolume(AudioManager.STREAM_RING) > 0; - timer.record("isVolumeOverZero"); - boolean shouldRingForContact = shouldRingForContact(foregroundCall.getContactUri()); - timer.record("shouldRingForContact"); - boolean isRingtonePresent = !(mRingtoneFactory.getRingtone(foregroundCall) == null); - timer.record("getRingtone"); - boolean isSelfManaged = foregroundCall.isSelfManaged(); - timer.record("isSelfManaged"); - boolean isSilentRingingRequested = foregroundCall.isSilentRingingRequested(); - timer.record("isSilentRingRequested"); - - boolean isRingerAudible = isVolumeOverZero && shouldRingForContact && isRingtonePresent; - timer.record("isRingerAudible"); - boolean hasExternalRinger = hasExternalRinger(foregroundCall); - timer.record("hasExternalRinger"); - // Don't do call waiting operations or vibration unless these are false. - boolean isTheaterModeOn = mSystemSettingsUtil.isTheaterModeOn(mContext); - timer.record("isTheaterModeOn"); - boolean letDialerHandleRinging = mInCallController.doesConnectedDialerSupportRinging(); - timer.record("letDialerHandleRinging"); - - Log.i(this, "startRinging timings: " + timer); - boolean endEarly = isTheaterModeOn || letDialerHandleRinging || isSelfManaged || - hasExternalRinger || isSilentRingingRequested; + // Use completable future to establish a timeout, not intent to make these work outside the + // main thread asynchronously + // TODO: moving these RingerAttributes calculation out of Telecom lock to avoid blocking. + CompletableFuture<RingerAttributes> ringerAttributesFuture = CompletableFuture + .supplyAsync(() -> getRingerAttributes(foregroundCall, isHfpDeviceAttached), + new LoggedHandlerExecutor(getHandler(), "R.sR", null)); + + RingerAttributes attributes = null; + try { + attributes = ringerAttributesFuture.get( + RINGER_ATTRIBUTES_TIMEOUT, TimeUnit.MILLISECONDS); + } catch (ExecutionException | InterruptedException | TimeoutException e) { + // Keep attributs as null + Log.i(this, "getAttributes error: " + e); + } - // Acquire audio focus under any of the following conditions: - // 1. Should ring for contact and there's an HFP device attached - // 2. Volume is over zero, we should ring for the contact, and there's a audible ringtone - // present. - // 3. The call is self-managed. - boolean shouldAcquireAudioFocus = - isRingerAudible || (isHfpDeviceAttached && shouldRingForContact) || isSelfManaged; + if (attributes == null) { + Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "RingerAttributes error"); + return false; + } - if (endEarly) { - if (letDialerHandleRinging) { + if (attributes.isEndEarly()) { + if (attributes.letDialerHandleRinging()) { Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Dialer handles"); } - if (isSilentRingingRequested) { + if (attributes.isSilentRingingRequested()) { Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Silent ringing " + "requested"); } - Log.i(this, "Ending early -- isTheaterModeOn=%s, letDialerHandleRinging=%s, " + - "isSelfManaged=%s, hasExternalRinger=%s, silentRingingRequested=%s", - isTheaterModeOn, letDialerHandleRinging, isSelfManaged, hasExternalRinger, - isSilentRingingRequested); if (mBlockOnRingingFuture != null) { mBlockOnRingingFuture.complete(null); } - return shouldAcquireAudioFocus; + return attributes.shouldAcquireAudioFocus(); } stopCallWaiting(); @@ -276,7 +264,7 @@ public class Ringer { CompletableFuture<Boolean> hapticsFuture = null; // Determine if the settings and DND mode indicate that the vibrator can be used right now. boolean isVibratorEnabled = isVibratorEnabled(mContext, foregroundCall); - if (isRingerAudible) { + if (attributes.isRingerAudible()) { mRingingCall = foregroundCall; Log.addEvent(foregroundCall, LogUtils.Events.START_RINGER); // Because we wait until a contact info query to complete before processing a @@ -309,15 +297,14 @@ public class Ringer { effect = getVibrationEffectForCall(mRingtoneFactory, foregroundCall); } } else { - String reason = String.format( - "isVolumeOverZero=%s, shouldRingForContact=%s, isRingtonePresent=%s", - isVolumeOverZero, shouldRingForContact, isRingtonePresent); - Log.i(this, "startRinging: skipping because ringer would not be audible. " + reason); - Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Inaudible: " + reason); + Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Inaudible: " + + attributes.getInaudibleReason()); effect = mDefaultVibrationEffect; } if (hapticsFuture != null) { + final boolean shouldRingForContact = attributes.shouldRingForContact(); + final boolean isRingerAudible = attributes.isRingerAudible(); mVibrateFuture = hapticsFuture.thenAccept(isUsingAudioCoupledHaptics -> { if (!isUsingAudioCoupledHaptics || !mIsHapticPlaybackSupportedByDevice) { Log.i(this, "startRinging: fileHasHaptics=%b, hapticsSupported=%b", @@ -342,11 +329,11 @@ public class Ringer { mBlockOnRingingFuture.complete(null); } Log.w(this, "startRinging: No haptics future; fallback to default behavior"); - maybeStartVibration(foregroundCall, shouldRingForContact, effect, isVibratorEnabled, - isRingerAudible); + maybeStartVibration(foregroundCall, attributes.shouldRingForContact(), effect, + isVibratorEnabled, attributes.isRingerAudible()); } - return shouldAcquireAudioFocus; + return attributes.shouldAcquireAudioFocus(); } private void maybeStartVibration(Call foregroundCall, boolean shouldRingForContact, @@ -517,4 +504,75 @@ public class Ringer { return mSystemSettingsUtil.canVibrateWhenRinging(context) || mSystemSettingsUtil.applyRampingRinger(context); } + + private RingerAttributes getRingerAttributes(Call call, boolean isHfpDeviceAttached) { + RingerAttributes.Builder builder = new RingerAttributes.Builder(); + + LogUtils.EventTimer timer = new EventTimer(); + + boolean isVolumeOverZero = mAudioManager.getStreamVolume(AudioManager.STREAM_RING) > 0; + timer.record("isVolumeOverZero"); + boolean shouldRingForContact = shouldRingForContact(call.getHandle()); + timer.record("shouldRingForContact"); + boolean isRingtonePresent = !(mRingtoneFactory.getRingtone(call) == null); + timer.record("getRingtone"); + boolean isSelfManaged = call.isSelfManaged(); + timer.record("isSelfManaged"); + boolean isSilentRingingRequested = call.isSilentRingingRequested(); + timer.record("isSilentRingRequested"); + + boolean isRingerAudible = isVolumeOverZero && shouldRingForContact && isRingtonePresent; + timer.record("isRingerAudible"); + String inaudibleReason = ""; + if (!isRingerAudible) { + inaudibleReason = String.format( + "isVolumeOverZero=%s, shouldRingForContact=%s, isRingtonePresent=%s", + isVolumeOverZero, shouldRingForContact, isRingtonePresent); + } + + boolean hasExternalRinger = hasExternalRinger(call); + timer.record("hasExternalRinger"); + // Don't do call waiting operations or vibration unless these are false. + boolean isTheaterModeOn = mSystemSettingsUtil.isTheaterModeOn(mContext); + timer.record("isTheaterModeOn"); + boolean letDialerHandleRinging = mInCallController.doesConnectedDialerSupportRinging(); + timer.record("letDialerHandleRinging"); + + Log.i(this, "startRinging timings: " + timer); + boolean endEarly = isTheaterModeOn || letDialerHandleRinging || isSelfManaged || + hasExternalRinger || isSilentRingingRequested; + + if (endEarly) { + Log.i(this, "Ending early -- isTheaterModeOn=%s, letDialerHandleRinging=%s, " + + "isSelfManaged=%s, hasExternalRinger=%s, silentRingingRequested=%s", + isTheaterModeOn, letDialerHandleRinging, isSelfManaged, hasExternalRinger, + isSilentRingingRequested); + } + + // Acquire audio focus under any of the following conditions: + // 1. Should ring for contact and there's an HFP device attached + // 2. Volume is over zero, we should ring for the contact, and there's a audible ringtone + // present. + // 3. The call is self-managed. + boolean shouldAcquireAudioFocus = + isRingerAudible || (isHfpDeviceAttached && shouldRingForContact) || isSelfManaged; + + return builder.setEndEarly(endEarly) + .setLetDialerHandleRinging(letDialerHandleRinging) + .setAcquireAudioFocus(shouldAcquireAudioFocus) + .setRingerAudible(isRingerAudible) + .setInaudibleReason(inaudibleReason) + .setShouldRingForContact(shouldRingForContact) + .setSilentRingingRequested(isSilentRingingRequested) + .build(); + } + + private Handler getHandler() { + if (mHandler == null) { + HandlerThread handlerThread = new HandlerThread("Ringer"); + handlerThread.start(); + mHandler = handlerThread.getThreadHandler(); + } + return mHandler; + } } diff --git a/src/com/android/server/telecom/RingerAttributes.java b/src/com/android/server/telecom/RingerAttributes.java new file mode 100644 index 000000000..840d815d1 --- /dev/null +++ b/src/com/android/server/telecom/RingerAttributes.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2020 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; + +public class RingerAttributes { + public static class Builder { + private boolean mEndEarly; + private boolean mLetDialerHandleRinging; + private boolean mAcquireAudioFocus; + private boolean mRingerAudible; + private String mInaudibleReason; + private boolean mShouldRingForContact; + private boolean mSilentRingingRequested; + + public RingerAttributes.Builder setEndEarly(boolean endEarly) { + mEndEarly = endEarly; + return this; + } + + public RingerAttributes.Builder setLetDialerHandleRinging(boolean letDialerHandleRinging) { + mLetDialerHandleRinging = letDialerHandleRinging; + return this; + } + + public RingerAttributes.Builder setAcquireAudioFocus(boolean acquireAudioFocus) { + mAcquireAudioFocus = acquireAudioFocus; + return this; + } + + public RingerAttributes.Builder setRingerAudible(boolean ringerAudible) { + mRingerAudible = ringerAudible; + return this; + } + + public RingerAttributes.Builder setInaudibleReason(String inaudibleReason) { + mInaudibleReason = inaudibleReason; + return this; + } + + public RingerAttributes.Builder setShouldRingForContact(boolean shouldRingForContact) { + mShouldRingForContact = shouldRingForContact; + return this; + } + + public RingerAttributes.Builder setSilentRingingRequested(boolean silentRingingRequested) { + mSilentRingingRequested = silentRingingRequested; + return this; + } + + public RingerAttributes build() { + return new RingerAttributes(mEndEarly, mLetDialerHandleRinging, mAcquireAudioFocus, + mRingerAudible, mInaudibleReason, mShouldRingForContact, + mSilentRingingRequested); + } + } + + private boolean mEndEarly; + private boolean mLetDialerHandleRinging; + private boolean mAcquireAudioFocus; + private boolean mRingerAudible; + private String mInaudibleReason; + private boolean mShouldRingForContact; + private boolean mSilentRingingRequested; + + private RingerAttributes(boolean endEarly, boolean letDialerHandleRinging, + boolean acquireAudioFocus, boolean ringerAudible, String inaudibleReason, + boolean shouldRingForContact, boolean silentRingingRequested) { + mEndEarly = endEarly; + mLetDialerHandleRinging = letDialerHandleRinging; + mAcquireAudioFocus = acquireAudioFocus; + mRingerAudible = ringerAudible; + mInaudibleReason = inaudibleReason; + mShouldRingForContact = shouldRingForContact; + mSilentRingingRequested = silentRingingRequested; + } + + public boolean isEndEarly() { + return mEndEarly; + } + + public boolean letDialerHandleRinging() { + return mLetDialerHandleRinging; + } + + public boolean shouldAcquireAudioFocus() { + return mAcquireAudioFocus; + } + + public boolean isRingerAudible() { + return mRingerAudible; + } + + public String getInaudibleReason() { + return mInaudibleReason; + } + + public boolean shouldRingForContact() { + return mShouldRingForContact; + } + + public boolean isSilentRingingRequested() { + return mSilentRingingRequested; + } +} diff --git a/src/com/android/server/telecom/RingtoneFactory.java b/src/com/android/server/telecom/RingtoneFactory.java index d416376bc..6a121f7c2 100644 --- a/src/com/android/server/telecom/RingtoneFactory.java +++ b/src/com/android/server/telecom/RingtoneFactory.java @@ -75,7 +75,12 @@ public class RingtoneFactory { if(ringtoneUri != null && userContext != null) { // Ringtone URI is explicitly specified. First, try to create a Ringtone with that. - ringtone = RingtoneManager.getRingtone(userContext, ringtoneUri, volumeShaperConfig); + try { + ringtone = RingtoneManager.getRingtone(userContext, ringtoneUri, + volumeShaperConfig); + } catch (NullPointerException npe) { + Log.e(this, npe, "getRingtone: NPE while getting ringtone."); + } } if(ringtone == null) { // Contact didn't specify ringtone or custom Ringtone creation failed. Get default @@ -97,8 +102,12 @@ public class RingtoneFactory { if (defaultRingtoneUri == null) { return null; } - ringtone = RingtoneManager.getRingtone( - contextToUse, defaultRingtoneUri, volumeShaperConfig); + try { + ringtone = RingtoneManager.getRingtone( + contextToUse, defaultRingtoneUri, volumeShaperConfig); + } catch (NullPointerException npe) { + Log.e(this, npe, "getRingtone: NPE while getting ringtone."); + } } if (ringtone != null) { ringtone.setAudioAttributes(new AudioAttributes.Builder() diff --git a/src/com/android/server/telecom/SystemSettingsUtil.java b/src/com/android/server/telecom/SystemSettingsUtil.java index f104f2720..7baae0579 100644 --- a/src/com/android/server/telecom/SystemSettingsUtil.java +++ b/src/com/android/server/telecom/SystemSettingsUtil.java @@ -40,18 +40,19 @@ public class SystemSettingsUtil { } public boolean canVibrateWhenRinging(Context context) { - return Settings.System.getInt(context.getContentResolver(), - Settings.System.VIBRATE_WHEN_RINGING, 0) != 0; + return Settings.System.getIntForUser(context.getContentResolver(), + Settings.System.VIBRATE_WHEN_RINGING, 0, context.getUserId()) != 0; } public boolean isEnhancedCallBlockingEnabled(Context context) { - return Settings.System.getInt(context.getContentResolver(), - Settings.System.DEBUG_ENABLE_ENHANCED_CALL_BLOCKING, 0) != 0; + return Settings.System.getIntForUser(context.getContentResolver(), + Settings.System.DEBUG_ENABLE_ENHANCED_CALL_BLOCKING, 0, context.getUserId()) != 0; } public boolean setEnhancedCallBlockingEnabled(Context context, boolean enabled) { - return Settings.System.putInt(context.getContentResolver(), - Settings.System.DEBUG_ENABLE_ENHANCED_CALL_BLOCKING, enabled ? 1 : 0); + return Settings.System.putIntForUser(context.getContentResolver(), + Settings.System.DEBUG_ENABLE_ENHANCED_CALL_BLOCKING, enabled ? 1 : 0, + context.getUserId()); } public boolean applyRampingRinger(Context context) { diff --git a/src/com/android/server/telecom/SystemStateHelper.java b/src/com/android/server/telecom/SystemStateHelper.java index f44eb395e..dd978c23b 100644 --- a/src/com/android/server/telecom/SystemStateHelper.java +++ b/src/com/android/server/telecom/SystemStateHelper.java @@ -16,6 +16,7 @@ package com.android.server.telecom; +import android.annotation.NonNull; import android.app.UiModeManager; import android.content.BroadcastReceiver; import android.content.Context; @@ -38,7 +39,7 @@ import java.util.concurrent.atomic.AtomicBoolean; /** * Provides various system states to the rest of the telecom codebase. */ -public class SystemStateHelper { +public class SystemStateHelper implements UiModeManager.OnProjectionStateChangedListener { public interface SystemStateListener { /** * Listener method to inform interested parties when a package name requests to enter or @@ -51,6 +52,19 @@ public class SystemStateHelper { void onCarModeChanged(int priority, String packageName, boolean isCarMode); /** + * Listener method to inform interested parties when a package has set automotive projection + * state. + * @param automotiveProjectionPackage the package that set automotive projection. + */ + void onAutomotiveProjectionStateSet(String automotiveProjectionPackage); + + /** + * Listener method to inform interested parties when automotive projection state has been + * cleared. + */ + void onAutomotiveProjectionStateReleased(); + + /** * Notifies when a package has been uninstalled. * @param packageName the package name of the uninstalled package */ @@ -61,7 +75,7 @@ public class SystemStateHelper { private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - Log.startSession("SSP.oR"); + Log.startSession("SSH.oR"); try { synchronized (mLock) { String action = intent.getAction(); @@ -103,8 +117,26 @@ public class SystemStateHelper { } }; + @Override + public void onProjectionStateChanged(int activeProjectionTypes, + @NonNull Set<String> projectingPackages) { + Log.startSession("SSH.oPSC"); + try { + synchronized (mLock) { + if (projectingPackages.isEmpty()) { + onReleaseAutomotiveProjection(); + } else { + onSetAutomotiveProjection(projectingPackages.iterator().next()); + } + } + } finally { + Log.endSession(); + } + + } + private Set<SystemStateListener> mListeners = new CopyOnWriteArraySet<>(); - private boolean mIsCarMode; + private boolean mIsCarModeOrProjectionActive; private final TelecomSystem.SyncRoot mLock; public SystemStateHelper(Context context, TelecomSystem.SyncRoot lock) { @@ -122,7 +154,9 @@ public class SystemStateHelper { Log.i(this, "Registering broadcast receiver: %s", intentFilter1); Log.i(this, "Registering broadcast receiver: %s", intentFilter2); - mIsCarMode = getSystemCarMode(); + mContext.getSystemService(UiModeManager.class).addOnProjectionStateChangedListener( + UiModeManager.PROJECTION_TYPE_AUTOMOTIVE, mContext.getMainExecutor(), this); + mIsCarModeOrProjectionActive = getSystemCarModeOrProjectionState(); } public void addListener(SystemStateListener listener) { @@ -135,8 +169,8 @@ public class SystemStateHelper { return mListeners.remove(listener); } - public boolean isCarMode() { - return mIsCarMode; + public boolean isCarModeOrProjectionActive() { + return mIsCarModeOrProjectionActive; } public boolean isDeviceAtEar() { @@ -221,7 +255,7 @@ public class SystemStateHelper { private void onEnterCarMode(int priority, String packageName) { Log.i(this, "Entering carmode"); - mIsCarMode = getSystemCarMode(); + mIsCarModeOrProjectionActive = getSystemCarModeOrProjectionState(); for (SystemStateListener listener : mListeners) { listener.onCarModeChanged(priority, packageName, true /* isCarMode */); } @@ -229,25 +263,44 @@ public class SystemStateHelper { private void onExitCarMode(int priority, String packageName) { Log.i(this, "Exiting carmode"); - mIsCarMode = getSystemCarMode(); + mIsCarModeOrProjectionActive = getSystemCarModeOrProjectionState(); for (SystemStateListener listener : mListeners) { listener.onCarModeChanged(priority, packageName, false /* isCarMode */); } } + private void onSetAutomotiveProjection(String packageName) { + Log.i(this, "Automotive projection set."); + mIsCarModeOrProjectionActive = getSystemCarModeOrProjectionState(); + for (SystemStateListener listener : mListeners) { + listener.onAutomotiveProjectionStateSet(packageName); + } + + } + + private void onReleaseAutomotiveProjection() { + Log.i(this, "Automotive projection released."); + mIsCarModeOrProjectionActive = getSystemCarModeOrProjectionState(); + for (SystemStateListener listener : mListeners) { + listener.onAutomotiveProjectionStateReleased(); + } + } + /** - * Checks the system for the current car mode. + * Checks the system for the current car projection state. * - * @return True if in car mode, false otherwise. + * @return True if projection is active, false otherwise. */ - private boolean getSystemCarMode() { - UiModeManager uiModeManager = - (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE); + private boolean getSystemCarModeOrProjectionState() { + UiModeManager uiModeManager = mContext.getSystemService(UiModeManager.class); if (uiModeManager != null) { - return uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR; + return uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR + || (uiModeManager.getActiveProjectionTypes() + & UiModeManager.PROJECTION_TYPE_AUTOMOTIVE) != 0; } + Log.w(this, "Got null UiModeManager, returning false."); return false; } } diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java index 0a284847e..e9b760a4e 100644 --- a/src/com/android/server/telecom/TelecomServiceImpl.java +++ b/src/com/android/server/telecom/TelecomServiceImpl.java @@ -30,6 +30,9 @@ import static android.Manifest.permission.WRITE_SECURE_SETTINGS; import android.Manifest; import android.app.ActivityManager; import android.app.AppOpsManager; +import android.app.UiModeManager; +import android.app.compat.CompatChanges; +import android.content.AttributionSource; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; @@ -58,7 +61,6 @@ import android.text.TextUtils; import android.util.EventLog; import com.android.internal.telecom.ITelecomService; -import com.android.internal.telephony.TelephonyPermissions; import com.android.internal.util.IndentingPrintWriter; import com.android.server.telecom.components.UserCallIntentProcessorFactory; import com.android.server.telecom.settings.BlockedNumbersActivity; @@ -315,9 +317,21 @@ public class TelecomServiceImpl { } @Override - public PhoneAccount getPhoneAccount(PhoneAccountHandle accountHandle) { + public PhoneAccount getPhoneAccount(PhoneAccountHandle accountHandle, + String callingPackage) { synchronized (mLock) { final UserHandle callingUserHandle = Binder.getCallingUserHandle(); + if (CompatChanges.isChangeEnabled( + TelecomManager.ENABLE_GET_PHONE_ACCOUNT_PERMISSION_PROTECTION, + callingPackage, Binder.getCallingUserHandle())) { + if (Binder.getCallingUid() != Process.SHELL_UID && + !canGetPhoneAccount(callingPackage, accountHandle)) { + SecurityException e = new SecurityException("getPhoneAccount API requires" + + "READ_PHONE_NUMBERS"); + Log.e(this, e, "getPhoneAccount %s", accountHandle); + throw e; + } + } long token = Binder.clearCallingIdentity(); try { Log.startSession("TSI.gPA"); @@ -327,7 +341,7 @@ public class TelecomServiceImpl { // profile's phone account handle. return mPhoneAccountRegistrar .getPhoneAccount(accountHandle, callingUserHandle, - /* acrossProfiles */ true); + /* acrossProfiles */ true); } catch (Exception e) { Log.e(this, e, "getPhoneAccount %s", accountHandle); throw e; @@ -457,11 +471,11 @@ public class TelecomServiceImpl { try { Log.startSession("TSI.gSCMFU"); final int callingUid = Binder.getCallingUid(); + if (user != ActivityManager.getCurrentUser()) { + enforceCrossUserPermission(callingUid); + } long token = Binder.clearCallingIdentity(); try { - if (user != ActivityManager.getCurrentUser()) { - enforceCrossUserPermission(callingUid); - } return mPhoneAccountRegistrar.getSimCallManager(UserHandle.of(user)); } finally { Binder.restoreCallingIdentity(token); @@ -838,10 +852,14 @@ public class TelecomServiceImpl { public boolean hasManageOngoingCallsPermission(String callingPackage) { try { Log.startSession("TSI.hMOCP"); - return PermissionChecker.checkPermissionForPreflight(mContext, - Manifest.permission.MANAGE_ONGOING_CALLS, - PermissionChecker.PID_UNKNOWN, Binder.getCallingUid(), - callingPackage) == PermissionChecker.PERMISSION_GRANTED; + return PermissionChecker.checkPermissionForDataDeliveryFromDataSource( + mContext, Manifest.permission.MANAGE_ONGOING_CALLS, + Binder.getCallingPid(), + new AttributionSource(mContext.getAttributionSource(), + new AttributionSource(Binder.getCallingUid(), + callingPackage, /*attributionTag*/ null)), + "Checking whether the caller has MANAGE_ONGOING_CALLS permission") + == PermissionChecker.PERMISSION_GRANTED; } finally { Log.endSession(); } @@ -898,12 +916,48 @@ public class TelecomServiceImpl { } /** - * @see TelecomManager#getCallState + * @see TelecomManager#getCallState() + * @deprecated this is only being kept due to an @UnsupportedAppUsage tag. Apps targeting + * API 31+ must use {@link #getCallStateUsingPackage(String, String)} below. */ + @Deprecated @Override public int getCallState() { try { - Log.startSession("TSI.getCallState"); + Log.startSession("TSI.getCallState(DEPRECATED)"); + if (CompatChanges.isChangeEnabled( + TelecomManager.ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION, + Binder.getCallingUid())) { + // Do not allow this API to be called on API version 31+, it should only be + // called on old apps using this Binder call directly. + throw new SecurityException("This method can only be used for applications " + + "targeting API version 30 or less."); + } + synchronized (mLock) { + return mCallsManager.getCallState(); + } + } finally { + Log.endSession(); + } + } + + /** + * @see TelecomManager#getCallState() + */ + @Override + public int getCallStateUsingPackage(String callingPackage, String callingFeatureId) { + try { + Log.startSession("TSI.getCallStateUsingPackage"); + if (CompatChanges.isChangeEnabled( + TelecomManager.ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION, callingPackage, + Binder.getCallingUserHandle())) { + // Bypass canReadPhoneState check if this is being called from SHELL UID + if (Binder.getCallingUid() != Process.SHELL_UID && !canReadPhoneState( + callingPackage, callingFeatureId, "getCallState")) { + throw new SecurityException("getCallState API requires READ_PHONE_STATE" + + " for API version 31+"); + } + } synchronized (mLock) { return mCallsManager.getCallState(); } @@ -948,7 +1002,7 @@ public class TelecomServiceImpl { long token = Binder.clearCallingIdentity(); try { - acceptRingingCallInternal(DEFAULT_VIDEO_STATE); + acceptRingingCallInternal(DEFAULT_VIDEO_STATE, packageName); } finally { Binder.restoreCallingIdentity(token); } @@ -971,7 +1025,7 @@ public class TelecomServiceImpl { long token = Binder.clearCallingIdentity(); try { - acceptRingingCallInternal(videoState); + acceptRingingCallInternal(videoState, packageName); } finally { Binder.restoreCallingIdentity(token); } @@ -1776,6 +1830,8 @@ public class TelecomServiceImpl { * {@link CallState#DISCONNECTED} or {@link CallState#DISCONNECTING} states. Stuck calls * during CTS cause cascading failures, so if the CTS test detects such a state, it should * call this method via a shell command to clean up before moving on to the next test. + * Also cleans up any pending futures related to + * {@link android.telecom.CallDiagnosticService}s. */ @Override public void cleanupStuckCalls() { @@ -1785,6 +1841,7 @@ public class TelecomServiceImpl { enforceShellOnly(Binder.getCallingUid(), "cleanupStuckCalls"); Binder.withCleanCallingIdentity(() -> { for (Call call : mCallsManager.getCalls()) { + call.cleanup(); if (call.getState() == CallState.DISCONNECTED || call.getState() == CallState.DISCONNECTING) { mCallsManager.markCallAsRemoved(call); @@ -1798,6 +1855,28 @@ public class TelecomServiceImpl { } } + /** + * A method intended for use in testing to reset car mode at all priorities. + * + * Runs during setup to avoid cascading failures from failing car mode CTS. + */ + @Override + public void resetCarMode() { + Log.startSession("TCI.rCM"); + try { + synchronized (mLock) { + enforceShellOnly(Binder.getCallingUid(), "resetCarMode"); + Binder.withCleanCallingIdentity(() -> { + UiModeManager uiModeManager = + mContext.getSystemService(UiModeManager.class); + uiModeManager.disableCarMode(UiModeManager.DISABLE_CAR_MODE_ALL_PRIORITIES); + }); + } + } finally { + Log.endSession(); + } + } + @Override public void setTestDefaultCallRedirectionApp(String packageName) { try { @@ -2053,9 +2132,16 @@ public class TelecomServiceImpl { return false; } - private void acceptRingingCallInternal(int videoState) { - Call call = mCallsManager.getFirstCallWithState(CallState.RINGING, CallState.SIMULATED_RINGING); + private void acceptRingingCallInternal(int videoState, String packageName) { + Call call = mCallsManager.getFirstCallWithState(CallState.RINGING, + CallState.SIMULATED_RINGING); if (call != null) { + if (call.isSelfManaged()) { + Log.addEvent(call, LogUtils.Events.REQUEST_ACCEPT, + "self-mgd accept ignored from " + packageName); + return; + } + if (videoState == DEFAULT_VIDEO_STATE || !isValidAcceptVideoState(videoState)) { videoState = call.getVideoState(); } @@ -2083,6 +2169,12 @@ public class TelecomServiceImpl { return false; } + if (call.isSelfManaged()) { + Log.addEvent(call, LogUtils.Events.REQUEST_DISCONNECT, + "self-mgd disconnect ignored from " + callingPackage); + return false; + } + if (call.getState() == CallState.RINGING || call.getState() == CallState.SIMULATED_RINGING) { mCallsManager.rejectCall(call, false /* rejectWithMessage */, null); @@ -2342,6 +2434,28 @@ public class TelecomServiceImpl { == AppOpsManager.MODE_ALLOWED; } + private boolean canGetPhoneAccount(String callingPackage, PhoneAccountHandle accountHandle) { + // Allow default dialer, system dialer and sim call manager to be able to do this without + // extra permission + try { + if (isPrivilegedDialerCalling(callingPackage) || isCallerSimCallManager( + accountHandle)) { + return true; + } + } catch (SecurityException e) { + // ignore + } + + try { + mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE, null); + return true; + } catch (SecurityException e) { + // Accessing phone state is gated by a special permission. + mContext.enforceCallingOrSelfPermission(READ_PHONE_NUMBERS, null); + return true; + } + } + private boolean isCallerSimCallManager(PhoneAccountHandle targetPhoneAccount) { long token = Binder.clearCallingIdentity(); PhoneAccountHandle accountHandle = null; diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java index a9f18d68b..a7442a9a4 100644 --- a/src/com/android/server/telecom/TelecomSystem.java +++ b/src/com/android/server/telecom/TelecomSystem.java @@ -31,6 +31,7 @@ import com.android.server.telecom.DefaultDialerCache.DefaultDialerManagerAdapter import com.android.server.telecom.ui.ToastFactory; import android.app.ActivityManager; +import android.bluetooth.BluetoothManager; import android.Manifest; import android.content.BroadcastReceiver; import android.content.Context; @@ -217,6 +218,7 @@ public class TelecomSystem { defaultDialerAdapter, roleManagerAdapter, mLock); Log.startSession("TS.init"); + // Wrap this in a try block to ensure session cleanup occurs in the case of error. try { mPhoneAccountRegistrar = new PhoneAccountRegistrar(mContext, defaultDialerCache, packageName -> AppLabelProxy.Util.getAppLabel( @@ -231,7 +233,7 @@ public class TelecomSystem { } }); BluetoothDeviceManager bluetoothDeviceManager = new BluetoothDeviceManager(mContext, - new BluetoothAdapterProxy()); + new BluetoothManager(mContext).getAdapter()); BluetoothRouteManager bluetoothRouteManager = new BluetoothRouteManager(mContext, mLock, bluetoothDeviceManager, new Timeouts.Adapter()); BluetoothStateReceiver bluetoothStateReceiver = new BluetoothStateReceiver( @@ -243,7 +245,8 @@ public class TelecomSystem { mMissedCallNotifier = missedCallNotifierImplFactory .makeMissedCallNotifierImpl(mContext, mPhoneAccountRegistrar, - defaultDialerCache, deviceIdleControllerAdapter); + defaultDialerCache, + deviceIdleControllerAdapter); DisconnectedCallNotifier.Factory disconnectedCallNotifierFactory = new DisconnectedCallNotifier.Default(); @@ -306,7 +309,8 @@ public class TelecomSystem { @Override public Toast makeText(Context context, int resId, int duration) { return Toast.makeText(context, context.getMainLooper(), - context.getString(resId), duration); + context.getString(resId), + duration); } @Override diff --git a/src/com/android/server/telecom/Timeouts.java b/src/com/android/server/telecom/Timeouts.java index 230959641..36caa2598 100644 --- a/src/com/android/server/telecom/Timeouts.java +++ b/src/com/android/server/telecom/Timeouts.java @@ -17,8 +17,13 @@ package com.android.server.telecom; import android.content.ContentResolver; +import android.provider.DeviceConfig; import android.provider.Settings; +import android.telecom.CallDiagnosticService; import android.telecom.CallRedirectionService; +import android.telecom.CallDiagnostics; +import android.telephony.ims.ImsReasonInfo; + import java.util.concurrent.TimeUnit; /** @@ -67,6 +72,14 @@ public final class Timeouts { public long getCallRecordingToneRepeatIntervalMillis(ContentResolver cr) { return Timeouts.getCallRecordingToneRepeatIntervalMillis(cr); } + + public long getCallDiagnosticServiceTimeoutMillis(ContentResolver cr) { + return Timeouts.getCallDiagnosticServiceTimeoutMillis(cr); + } + + public long getCallStartAppOpDebounceIntervalMillis() { + return Timeouts.getCallStartAppOpDebounceIntervalMillis(); + } } /** A prefix to use for all keys so to not clobber the global namespace. */ @@ -85,7 +98,8 @@ public final class Timeouts { * @return The timeout value from Settings or the default value if it hasn't been changed. */ private static long get(ContentResolver contentResolver, String key, long defaultValue) { - return Settings.Secure.getLong(contentResolver, PREFIX + key, defaultValue); + return Settings.Secure.getLongForUser(contentResolver, PREFIX + key, defaultValue, + contentResolver.getUserId()); } /** @@ -189,7 +203,7 @@ public final class Timeouts { /** * Returns the amount of time for an user-defined {@link CallRedirectionService}. * - * @param contentResolver The content resolved. + * @param contentResolver The content resolver. */ public static long getUserDefinedCallRedirectionTimeoutMillis(ContentResolver contentResolver) { return get(contentResolver, "user_defined_call_redirection_timeout", @@ -199,7 +213,7 @@ public final class Timeouts { /** * Returns the amount of time for a carrier {@link CallRedirectionService}. * - * @param contentResolver The content resolved. + * @param contentResolver The content resolver. */ public static long getCarrierCallRedirectionTimeoutMillis(ContentResolver contentResolver) { return get(contentResolver, "carrier_call_redirection_timeout", 5000L /* 5 seconds */); @@ -213,6 +227,21 @@ public final class Timeouts { } /** + * Returns the maximum amount of time a {@link CallDiagnosticService} is permitted to take to + * return back from {@link CallDiagnostics#onCallDisconnected(ImsReasonInfo)} and + * {@link CallDiagnostics#onCallDisconnected(int, int)}. + * @param contentResolver The resolver for the config option. + * @return The timeout in millis. + */ + public static long getCallDiagnosticServiceTimeoutMillis(ContentResolver contentResolver) { + return get(contentResolver, "call_diagnostic_service_timeout", 2000L /* 2 sec */); + } + + public static long getCallStartAppOpDebounceIntervalMillis() { + return DeviceConfig.getLong(DeviceConfig.NAMESPACE_PRIVACY, "app_op_debounce_time", 250L); + } + + /** * Returns the number of milliseconds for which the system should exempt the default dialer from * power save restrictions due to the dialer needing to handle a missed call notification * (update call log, check VVM, etc...). diff --git a/src/com/android/server/telecom/TtyManager.java b/src/com/android/server/telecom/TtyManager.java index dfddb8f0b..457ba363f 100644 --- a/src/com/android/server/telecom/TtyManager.java +++ b/src/com/android/server/telecom/TtyManager.java @@ -42,10 +42,11 @@ final class TtyManager implements WiredHeadsetManager.Listener { mWiredHeadsetManager = wiredHeadsetManager; mWiredHeadsetManager.addListener(this); - mPreferredTtyMode = Settings.Secure.getInt( + mPreferredTtyMode = Settings.Secure.getIntForUser( mContext.getContentResolver(), Settings.Secure.PREFERRED_TTY_MODE, - TelecomManager.TTY_MODE_OFF); + TelecomManager.TTY_MODE_OFF, + mContext.getUserId()); IntentFilter intentFilter = new IntentFilter( TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED); diff --git a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java index 7fd600c59..8e93a7519 100644 --- a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java +++ b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java @@ -26,8 +26,6 @@ import android.telecom.Log; import android.util.LocalLog; import com.android.internal.util.IndentingPrintWriter; -import com.android.server.telecom.BluetoothAdapterProxy; -import com.android.server.telecom.BluetoothHeadsetProxy; import java.util.ArrayList; import java.util.Collection; @@ -48,13 +46,12 @@ public class BluetoothDeviceManager { synchronized (mLock) { String logString; if (profile == BluetoothProfile.HEADSET) { - mBluetoothHeadsetService = - new BluetoothHeadsetProxy((BluetoothHeadset) proxy); - logString = "Got BluetoothHeadset: " + mBluetoothHeadsetService; + mBluetoothHeadset = (BluetoothHeadset) proxy; + logString = "Got BluetoothHeadset: " + mBluetoothHeadset; } else if (profile == BluetoothProfile.HEARING_AID) { - mBluetoothHearingAidService = (BluetoothHearingAid) proxy; + mBluetoothHearingAid = (BluetoothHearingAid) proxy; logString = "Got BluetoothHearingAid: " - + mBluetoothHearingAidService; + + mBluetoothHearingAid; } else { logString = "Connected to non-requested bluetooth service." + " Not changing bluetooth headset."; @@ -75,13 +72,13 @@ public class BluetoothDeviceManager { LinkedHashMap<String, BluetoothDevice> lostServiceDevices; String logString; if (profile == BluetoothProfile.HEADSET) { - mBluetoothHeadsetService = null; + mBluetoothHeadset = null; lostServiceDevices = mHfpDevicesByAddress; mBluetoothRouteManager.onActiveDeviceChanged(null, false); logString = "Lost BluetoothHeadset service. " + "Removing all tracked devices"; } else if (profile == BluetoothProfile.HEARING_AID) { - mBluetoothHearingAidService = null; + mBluetoothHearingAid = null; logString = "Lost BluetoothHearingAid service. " + "Removing all tracked devices."; lostServiceDevices = mHearingAidDevicesByAddress; @@ -117,14 +114,14 @@ public class BluetoothDeviceManager { private final Object mLock = new Object(); private BluetoothRouteManager mBluetoothRouteManager; - private BluetoothHeadsetProxy mBluetoothHeadsetService; - private BluetoothHearingAid mBluetoothHearingAidService; + private BluetoothHeadset mBluetoothHeadset; + private BluetoothHearingAid mBluetoothHearingAid; private BluetoothDevice mBluetoothHearingAidActiveDeviceCache; - private BluetoothAdapterProxy mBluetoothAdapterProxy; + private BluetoothAdapter mBluetoothAdapter; - public BluetoothDeviceManager(Context context, BluetoothAdapterProxy bluetoothAdapter) { + public BluetoothDeviceManager(Context context, BluetoothAdapter bluetoothAdapter) { if (bluetoothAdapter != null) { - mBluetoothAdapterProxy = bluetoothAdapter; + mBluetoothAdapter = bluetoothAdapter; bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener, BluetoothProfile.HEADSET); bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener, @@ -160,13 +157,12 @@ public class BluetoothDeviceManager { Set<Long> seenHiSyncIds = new LinkedHashSet<>(); // Add the left-most active device to the seen list so that we match up with the list // generated in BluetoothRouteManager. - if (mBluetoothHearingAidService != null) { - for (BluetoothDevice device : mBluetoothHearingAidService.getActiveDevices()) { - if (device != null) { - result.add(device); - seenHiSyncIds.add(mHearingAidDeviceSyncIds.getOrDefault(device, -1L)); - break; - } + for (BluetoothDevice device : mBluetoothAdapter.getActiveDevices( + BluetoothProfile.HEARING_AID)) { + if (device != null) { + result.add(device); + seenHiSyncIds.add(mHearingAidDeviceSyncIds.getOrDefault(device, -1L)); + break; } } synchronized (mLock) { @@ -182,20 +178,24 @@ public class BluetoothDeviceManager { return Collections.unmodifiableCollection(result); } - public BluetoothHeadsetProxy getHeadsetService() { - return mBluetoothHeadsetService; + public BluetoothHeadset getBluetoothHeadset() { + return mBluetoothHeadset; + } + + public BluetoothAdapter getBluetoothAdapter() { + return mBluetoothAdapter; } - public BluetoothHearingAid getHearingAidService() { - return mBluetoothHearingAidService; + public BluetoothHearingAid getBluetoothHearingAid() { + return mBluetoothHearingAid; } - public void setHeadsetServiceForTesting(BluetoothHeadsetProxy bluetoothHeadset) { - mBluetoothHeadsetService = bluetoothHeadset; + public void setHeadsetServiceForTesting(BluetoothHeadset bluetoothHeadset) { + mBluetoothHeadset = bluetoothHeadset; } public void setHearingAidServiceForTesting(BluetoothHearingAid bluetoothHearingAid) { - mBluetoothHearingAidService = bluetoothHearingAid; + mBluetoothHearingAid = bluetoothHearingAid; } void onDeviceConnected(BluetoothDevice device, boolean isHearingAid) { @@ -204,15 +204,15 @@ public class BluetoothDeviceManager { synchronized (mLock) { LinkedHashMap<String, BluetoothDevice> targetDeviceMap; if (isHearingAid) { - if (mBluetoothHearingAidService == null) { + if (mBluetoothHearingAid == null) { Log.w(this, "Hearing aid service null when receiving device added broadcast"); return; } - long hiSyncId = mBluetoothHearingAidService.getHiSyncId(device); + long hiSyncId = mBluetoothHearingAid.getHiSyncId(device); mHearingAidDeviceSyncIds.put(device, hiSyncId); targetDeviceMap = mHearingAidDevicesByAddress; } else { - if (mBluetoothHeadsetService == null) { + if (mBluetoothHeadset == null) { Log.w(this, "Headset service null when receiving device added broadcast"); return; } @@ -244,24 +244,20 @@ public class BluetoothDeviceManager { } public void disconnectAudio() { - if (mBluetoothHearingAidService == null) { - Log.w(this, "Trying to disconnect audio but no hearing aid service exists"); - } else { - for (BluetoothDevice device : mBluetoothHearingAidService.getActiveDevices()) { - if (device != null) { - mBluetoothAdapterProxy.setActiveDevice(null, - BluetoothAdapter.ACTIVE_DEVICE_ALL); - } + for (BluetoothDevice device: mBluetoothAdapter.getActiveDevices( + BluetoothProfile.HEARING_AID)) { + if (device != null) { + mBluetoothAdapter.setActiveDevice(null, BluetoothAdapter.ACTIVE_DEVICE_ALL); } } disconnectSco(); } public void disconnectSco() { - if (mBluetoothHeadsetService == null) { + if (mBluetoothHeadset == null) { Log.w(this, "Trying to disconnect audio but no headset service exists."); } else { - mBluetoothHeadsetService.disconnectAudio(); + mBluetoothHeadset.disconnectAudio(); } } @@ -269,27 +265,27 @@ public class BluetoothDeviceManager { // or a HFP device, and using the proper BT API. public boolean connectAudio(String address) { if (mHearingAidDevicesByAddress.containsKey(address)) { - if (mBluetoothHearingAidService == null) { + if (mBluetoothHearingAid == null) { Log.w(this, "Attempting to turn on audio when the hearing aid service is null"); return false; } - return mBluetoothAdapterProxy.setActiveDevice( + return mBluetoothAdapter.setActiveDevice( mHearingAidDevicesByAddress.get(address), BluetoothAdapter.ACTIVE_DEVICE_ALL); } else if (mHfpDevicesByAddress.containsKey(address)) { BluetoothDevice device = mHfpDevicesByAddress.get(address); - if (mBluetoothHeadsetService == null) { + if (mBluetoothHeadset == null) { Log.w(this, "Attempting to turn on audio when the headset service is null"); return false; } - boolean success = mBluetoothAdapterProxy.setActiveDevice(device, + boolean success = mBluetoothAdapter.setActiveDevice(device, BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL); if (!success) { Log.w(this, "Couldn't set active device to %s", address); return false; } - if (!mBluetoothHeadsetService.isAudioOn()) { - return mBluetoothHeadsetService.connectAudio(); + if (!mBluetoothHeadset.isAudioOn()) { + return mBluetoothHeadset.connectAudio(); } return true; } else { @@ -299,20 +295,18 @@ public class BluetoothDeviceManager { } public void cacheHearingAidDevice() { - if (mBluetoothHearingAidService != null) { - for (BluetoothDevice device : mBluetoothHearingAidService.getActiveDevices()) { - if (device != null) { - mBluetoothHearingAidActiveDeviceCache = device; - } - } + for (BluetoothDevice device : mBluetoothAdapter.getActiveDevices( + BluetoothProfile.HEARING_AID)) { + if (device != null) { + mBluetoothHearingAidActiveDeviceCache = device; + } } } public void restoreHearingAidDevice() { - if (mBluetoothHearingAidActiveDeviceCache != null && mBluetoothHearingAidService != null) { - mBluetoothAdapterProxy.setActiveDevice( - mBluetoothHearingAidActiveDeviceCache, - BluetoothAdapter.ACTIVE_DEVICE_ALL); + if (mBluetoothHearingAidActiveDeviceCache != null) { + mBluetoothAdapter.setActiveDevice(mBluetoothHearingAidActiveDeviceCache, + BluetoothAdapter.ACTIVE_DEVICE_ALL); mBluetoothHearingAidActiveDeviceCache = null; } } diff --git a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java index 721199030..81f4ab6f3 100644 --- a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java +++ b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java @@ -16,9 +16,11 @@ package com.android.server.telecom.bluetooth; +import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; import android.bluetooth.BluetoothHearingAid; +import android.bluetooth.BluetoothProfile; import android.content.Context; import android.os.Message; import android.telecom.Log; @@ -30,7 +32,6 @@ import com.android.internal.os.SomeArgs; import com.android.internal.util.IState; import com.android.internal.util.State; import com.android.internal.util.StateMachine; -import com.android.server.telecom.BluetoothHeadsetProxy; import com.android.server.telecom.TelecomSystem; import com.android.server.telecom.Timeouts; @@ -701,19 +702,28 @@ public class BluetoothRouteManager extends StateMachine { */ @VisibleForTesting public BluetoothDevice getBluetoothAudioConnectedDevice() { - BluetoothHeadsetProxy bluetoothHeadset = mDeviceManager.getHeadsetService(); - BluetoothHearingAid bluetoothHearingAid = mDeviceManager.getHearingAidService(); + BluetoothAdapter bluetoothAdapter = mDeviceManager.getBluetoothAdapter(); + BluetoothHeadset bluetoothHeadset = mDeviceManager.getBluetoothHeadset(); + BluetoothHearingAid bluetoothHearingAid = mDeviceManager.getBluetoothHearingAid(); BluetoothDevice hfpAudioOnDevice = null; BluetoothDevice hearingAidActiveDevice = null; + if (bluetoothAdapter == null) { + Log.i(this, "getBluetoothAudioConnectedDevice: no adapter available."); + return null; + } if (bluetoothHeadset == null && bluetoothHearingAid == null) { Log.i(this, "getBluetoothAudioConnectedDevice: no service available."); return null; } if (bluetoothHeadset != null) { - hfpAudioOnDevice = bluetoothHeadset.getActiveDevice(); + for (BluetoothDevice device : bluetoothAdapter.getActiveDevices( + BluetoothProfile.HEADSET)) { + hfpAudioOnDevice = device; + break; + } if (bluetoothHeadset.getAudioState(hfpAudioOnDevice) == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { @@ -722,7 +732,8 @@ public class BluetoothRouteManager extends StateMachine { } if (bluetoothHearingAid != null) { - for (BluetoothDevice device : bluetoothHearingAid.getActiveDevices()) { + for (BluetoothDevice device : bluetoothAdapter.getActiveDevices( + BluetoothProfile.HEARING_AID)) { if (device != null) { hearingAidActiveDevice = device; break; @@ -751,7 +762,7 @@ public class BluetoothRouteManager extends StateMachine { */ @VisibleForTesting public boolean isInbandRingingEnabled() { - BluetoothHeadsetProxy bluetoothHeadset = mDeviceManager.getHeadsetService(); + BluetoothHeadset bluetoothHeadset = mDeviceManager.getBluetoothHeadset(); if (bluetoothHeadset == null) { Log.i(this, "isInbandRingingEnabled: no headset service available."); return false; diff --git a/src/com/android/server/telecom/callfiltering/CallFilteringResult.java b/src/com/android/server/telecom/callfiltering/CallFilteringResult.java index d95d578f4..84ce4d4e2 100644 --- a/src/com/android/server/telecom/callfiltering/CallFilteringResult.java +++ b/src/com/android/server/telecom/callfiltering/CallFilteringResult.java @@ -18,6 +18,7 @@ package com.android.server.telecom.callfiltering; import android.provider.CallLog; import android.provider.CallLog.Calls; +import android.telecom.CallScreeningService; import android.text.TextUtils; import java.util.Objects; @@ -34,6 +35,8 @@ public class CallFilteringResult { private int mCallBlockReason = Calls.BLOCK_REASON_NOT_BLOCKED; private CharSequence mCallScreeningAppName = null; private String mCallScreeningComponentName = null; + private CallScreeningService.ParcelableCallResponse mCallScreeningResponse = null; + private boolean mIsResponseFromSystemDialer = false; public Builder setShouldAllowCall(boolean shouldAllowCall) { mShouldAllowCall = shouldAllowCall; @@ -80,6 +83,13 @@ public class CallFilteringResult { return this; } + public Builder setCallScreeningResponse( + CallScreeningService.ParcelableCallResponse response, boolean isFromSystemDialer) { + mCallScreeningResponse = response; + mIsResponseFromSystemDialer = isFromSystemDialer; + return this; + } + public Builder setContactExists(boolean contactExists) { mContactExists = contactExists; return this; @@ -96,14 +106,16 @@ public class CallFilteringResult { .setShouldScreenViaAudio(result.shouldScreenViaAudio) .setCallScreeningAppName(result.mCallScreeningAppName) .setCallScreeningComponentName(result.mCallScreeningComponentName) + .setCallScreeningResponse(result.mCallScreeningResponse, + result.mIsResponseFromSystemDialer) .setContactExists(result.contactExists); } public CallFilteringResult build() { return new CallFilteringResult(mShouldAllowCall, mShouldReject, mShouldSilence, mShouldAddToCallLog, mShouldShowNotification, mCallBlockReason, - mCallScreeningAppName, mCallScreeningComponentName, mShouldScreenViaAudio, - mContactExists); + mCallScreeningAppName, mCallScreeningComponentName, mCallScreeningResponse, + mIsResponseFromSystemDialer, mShouldScreenViaAudio, mContactExists); } } @@ -116,11 +128,15 @@ public class CallFilteringResult { public int mCallBlockReason; public CharSequence mCallScreeningAppName; public String mCallScreeningComponentName; + public CallScreeningService.ParcelableCallResponse mCallScreeningResponse; + public boolean mIsResponseFromSystemDialer; public boolean contactExists; private CallFilteringResult(boolean shouldAllowCall, boolean shouldReject, boolean shouldSilence, boolean shouldAddToCallLog, boolean shouldShowNotification, int callBlockReason, CharSequence callScreeningAppName, String callScreeningComponentName, + CallScreeningService.ParcelableCallResponse callScreeningResponse, + boolean isResponseFromSystemDialer, boolean shouldScreenViaAudio, boolean contactExists) { this.shouldAllowCall = shouldAllowCall; this.shouldReject = shouldReject; @@ -131,6 +147,8 @@ public class CallFilteringResult { this.mCallBlockReason = callBlockReason; this.mCallScreeningAppName = callScreeningAppName; this.mCallScreeningComponentName = callScreeningComponentName; + this.mCallScreeningResponse = callScreeningResponse; + this.mIsResponseFromSystemDialer = isResponseFromSystemDialer; this.contactExists = contactExists; } @@ -148,25 +166,25 @@ public class CallFilteringResult { if (isBlockedByProvider(mCallBlockReason)) { return getCombinedCallFilteringResult(other, mCallBlockReason, - null /*callScreeningAppName*/, null /*callScreeningComponentName*/); + null /*callScreeningAppName*/, null /*callScreeningComponentName*/); } else if (isBlockedByProvider(other.mCallBlockReason)) { return getCombinedCallFilteringResult(other, other.mCallBlockReason, - null /*callScreeningAppName*/, null /*callScreeningComponentName*/); + null /*callScreeningAppName*/, null /*callScreeningComponentName*/); } if (mCallBlockReason == Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL - || other.mCallBlockReason == Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL) { + || other.mCallBlockReason == Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL) { return getCombinedCallFilteringResult(other, Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL, - null /*callScreeningAppName*/, null /*callScreeningComponentName*/); + null /*callScreeningAppName*/, null /*callScreeningComponentName*/); } if (shouldReject && mCallBlockReason == CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE) { return getCombinedCallFilteringResult(other, Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, - mCallScreeningAppName, mCallScreeningComponentName); + mCallScreeningAppName, mCallScreeningComponentName); } else if (other.shouldReject && other.mCallBlockReason == CallLog.Calls - .BLOCK_REASON_CALL_SCREENING_SERVICE) { + .BLOCK_REASON_CALL_SCREENING_SERVICE) { return getCombinedCallFilteringResult(other, Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, - other.mCallScreeningAppName, other.mCallScreeningComponentName); + other.mCallScreeningAppName, other.mCallScreeningComponentName); } if (shouldScreenViaAudio) { @@ -177,15 +195,16 @@ public class CallFilteringResult { other.mCallScreeningAppName, other.mCallScreeningComponentName); } - return new Builder() + Builder b = new Builder() .setShouldAllowCall(shouldAllowCall && other.shouldAllowCall) .setShouldReject(shouldReject || other.shouldReject) .setShouldSilence(shouldSilence || other.shouldSilence) .setShouldAddToCallLog(shouldAddToCallLog && other.shouldAddToCallLog) .setShouldShowNotification(shouldShowNotification && other.shouldShowNotification) .setShouldScreenViaAudio(shouldScreenViaAudio || other.shouldScreenViaAudio) - .setContactExists(contactExists || other.contactExists) - .build(); + .setContactExists(contactExists || other.contactExists); + combineScreeningResponses(b, this, other); + return b.build(); } private boolean isBlockedByProvider(int blockReason) { @@ -201,8 +220,9 @@ public class CallFilteringResult { } private CallFilteringResult getCombinedCallFilteringResult(CallFilteringResult other, - int callBlockReason, CharSequence callScreeningAppName, String callScreeningComponentName) { - return new Builder() + int callBlockReason, CharSequence callScreeningAppName, + String callScreeningComponentName) { + Builder b = new Builder() .setShouldAllowCall(shouldAllowCall && other.shouldAllowCall) .setShouldReject(shouldReject || other.shouldReject) .setShouldSilence(shouldSilence || other.shouldSilence) @@ -212,10 +232,33 @@ public class CallFilteringResult { .setCallBlockReason(callBlockReason) .setCallScreeningAppName(callScreeningAppName) .setCallScreeningComponentName(callScreeningComponentName) - .setContactExists(contactExists || other.contactExists) - .build(); + .setContactExists(contactExists || other.contactExists); + combineScreeningResponses(b, this, other); + return b.build(); } + private static void combineScreeningResponses(Builder builder, CallFilteringResult r1, + CallFilteringResult r2) { + if (r1.mIsResponseFromSystemDialer) { + builder.setCallScreeningResponse(r1.mCallScreeningResponse, true); + builder.setCallScreeningComponentName(r1.mCallScreeningComponentName); + builder.setCallScreeningAppName(r1.mCallScreeningAppName); + } else if (r2.mIsResponseFromSystemDialer) { + builder.setCallScreeningResponse(r2.mCallScreeningResponse, true); + builder.setCallScreeningComponentName(r2.mCallScreeningComponentName); + builder.setCallScreeningAppName(r2.mCallScreeningAppName); + } else { + if (r1.mCallScreeningResponse != null) { + builder.setCallScreeningResponse(r1.mCallScreeningResponse, false); + builder.setCallScreeningComponentName(r1.mCallScreeningComponentName); + builder.setCallScreeningAppName(r1.mCallScreeningAppName); + } else { + builder.setCallScreeningResponse(r2.mCallScreeningResponse, false); + builder.setCallScreeningComponentName(r2.mCallScreeningComponentName); + builder.setCallScreeningAppName(r2.mCallScreeningAppName); + } + } + } @Override public boolean equals(Object o) { diff --git a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java index 486cd8d80..4a308e0c9 100644 --- a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java +++ b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java @@ -25,6 +25,7 @@ import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.provider.CallLog; +import android.telecom.CallScreeningService; import android.telecom.Log; import android.telecom.TelecomManager; @@ -64,15 +65,44 @@ public class CallScreeningServiceFilter extends CallFilter { } @Override - public void allowCall(String callId) { - Long token = Binder.clearCallingIdentity(); + public void onScreeningResponse(String callId, ComponentName componentName, + CallScreeningService.ParcelableCallResponse callResponse) { + if (callResponse == null) { + Log.w(this, "Null responses are only supposed to happen for outgoing calls"); + return; + } + if (callResponse.shouldDisallowCall()) { + disallowCall(callId, componentName, callResponse); + } else if (callResponse.shouldSilenceCall()) { + silenceCall(callId, componentName, callResponse); + } else if (callResponse.shouldScreenCallViaAudioProcessing()) { + screenCallFurther(callId, componentName, callResponse); + } else { + allowCall(callId, componentName, callResponse); + } + } + + public void allowCall(String callId, ComponentName componentName, + CallScreeningService.ParcelableCallResponse response) { + long token = Binder.clearCallingIdentity(); Log.startSession("NCSSF.aC"); try { if (mCall == null || (!mCall.getId().equals(callId))) { Log.w(this, "allowCall, unknown call id: %s", callId); } - Log.addEvent(mCall, LogUtils.Events.SCREENING_COMPLETED, mPriorStageResult); - mResultFuture.complete(mPriorStageResult); + CallFilteringResult result = new CallFilteringResult.Builder() + .setShouldAllowCall(true) + .setShouldReject(false) + .setShouldSilence(false) + .setShouldAddToCallLog(mPriorStageResult.shouldAddToCallLog) + .setShouldShowNotification(mPriorStageResult.shouldShowNotification) + .setCallScreeningAppName(mAppName) + .setCallScreeningComponentName(componentName.flattenToString()) + .setCallScreeningResponse(response, isSystemDialer()) + .setContactExists(mPriorStageResult.contactExists) + .build(); + Log.addEvent(mCall, LogUtils.Events.SCREENING_COMPLETED, result); + mResultFuture.complete(result); } finally { unbindCallScreeningService(); Binder.restoreCallingIdentity(token); @@ -80,24 +110,23 @@ public class CallScreeningServiceFilter extends CallFilter { } } - @Override - public void disallowCall(String callId, boolean shouldReject, - boolean shouldAddToCallLog, boolean shouldShowNotification, - ComponentName componentName) { + public void disallowCall(String callId, ComponentName componentName, + CallScreeningService.ParcelableCallResponse response) { long token = Binder.clearCallingIdentity(); Log.startSession("NCSSF.dC"); try { if (mCall != null && mCall.getId().equals(callId)) { CallFilteringResult result = new CallFilteringResult.Builder() .setShouldAllowCall(false) - .setShouldReject(shouldReject) + .setShouldReject(response.shouldRejectCall()) .setShouldSilence(false) - .setShouldAddToCallLog(shouldAddToCallLog + .setShouldAddToCallLog(!response.shouldSkipCallLog() || packageTypeShouldAdd(mPackagetype)) - .setShouldShowNotification(shouldShowNotification) + .setShouldShowNotification(!response.shouldSkipNotification()) .setCallBlockReason(CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE) .setCallScreeningAppName(mAppName) .setCallScreeningComponentName(componentName.flattenToString()) + .setCallScreeningResponse(response, isSystemDialer()) .setContactExists(mPriorStageResult.contactExists) .build(); Log.addEvent(mCall, LogUtils.Events.SCREENING_COMPLETED, result); @@ -113,8 +142,8 @@ public class CallScreeningServiceFilter extends CallFilter { } } - @Override - public void silenceCall(String callId) { + public void silenceCall(String callId, ComponentName componentName, + CallScreeningService.ParcelableCallResponse response) { long token = Binder.clearCallingIdentity(); Log.startSession("NCSSF.sC"); try { @@ -125,6 +154,9 @@ public class CallScreeningServiceFilter extends CallFilter { .setShouldSilence(true) .setShouldAddToCallLog(true) .setShouldShowNotification(true) + .setCallScreeningResponse(response, isSystemDialer()) + .setCallScreeningAppName(mAppName) + .setCallScreeningComponentName(componentName.flattenToString()) .setContactExists(mPriorStageResult.contactExists) .build(); Log.addEvent(mCall, LogUtils.Events.SCREENING_COMPLETED, result); @@ -140,8 +172,8 @@ public class CallScreeningServiceFilter extends CallFilter { } } - @Override - public void screenCallFurther(String callId) { + public void screenCallFurther(String callId, ComponentName componentName, + CallScreeningService.ParcelableCallResponse response) { if (mPackagetype != PACKAGE_TYPE_DEFAULT_DIALER) { throw new SecurityException("Only the default/system dialer may request screen via" + "background call audio"); @@ -158,6 +190,8 @@ public class CallScreeningServiceFilter extends CallFilter { .setShouldSilence(false) .setShouldScreenViaAudio(true) .setCallScreeningAppName(mAppName) + .setCallScreeningComponentName(componentName.flattenToString()) + .setCallScreeningResponse(response, isSystemDialer()) .setContactExists(mPriorStageResult.contactExists) .build(); Log.addEvent(mCall, LogUtils.Events.SCREENING_COMPLETED, result); diff --git a/src/com/android/server/telecom/callfiltering/IncomingCallFilterGraph.java b/src/com/android/server/telecom/callfiltering/IncomingCallFilterGraph.java index e3c68c9b7..9fa864e60 100644 --- a/src/com/android/server/telecom/callfiltering/IncomingCallFilterGraph.java +++ b/src/com/android/server/telecom/callfiltering/IncomingCallFilterGraph.java @@ -49,7 +49,7 @@ public class IncomingCallFilterGraph { private final HandlerThread mHandlerThread; private final TelecomSystem.SyncRoot mLock; private List<CallFilter> mFiltersList; - private CallFilter mDummyComplete; + private CallFilter mCompletionSentinel; private boolean mFinished; private CallFilteringResult mCurrentResult; private Context mContext; @@ -70,7 +70,7 @@ public class IncomingCallFilterGraph { scheduleFilter(filter); } } - if (mFilter.equals(mDummyComplete)) { + if (mFilter.equals(mCompletionSentinel)) { synchronized (mLock) { mFinished = true; mListener.onCallFilteringComplete(mCall, result, false); @@ -105,15 +105,15 @@ public class IncomingCallFilterGraph { public void performFiltering() { Log.addEvent(mCall, LogUtils.Events.FILTERING_INITIATED); CallFilter dummyStart = new CallFilter(); - mDummyComplete = new CallFilter(); + mCompletionSentinel = new CallFilter(); for (CallFilter filter : mFiltersList) { addEdge(dummyStart, filter); } for (CallFilter filter : mFiltersList) { - addEdge(filter, mDummyComplete); + addEdge(filter, mCompletionSentinel); } - addEdge(dummyStart, mDummyComplete); + addEdge(dummyStart, mCompletionSentinel); scheduleFilter(dummyStart); mHandler.postDelayed(new Runnable("ICFG.pF", mLock) { @@ -159,7 +159,11 @@ public class IncomingCallFilterGraph { startFuture.thenComposeAsync(filter::startFilterLookup, new LoggedHandlerExecutor(mHandler, "ICFG.sF", null)) .thenApplyAsync(postFilterTask::whenDone, - new LoggedHandlerExecutor(mHandler, "ICFG.sF", null)); + new LoggedHandlerExecutor(mHandler, "ICFG.sF", null)) + .exceptionally((t) -> { + Log.e(filter, t, "Encountered exception running filter"); + return null; + }); Log.i(TAG, "Filter %s scheduled.", filter); } diff --git a/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java b/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java index 34975aab3..adeb3113d 100644 --- a/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java +++ b/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java @@ -305,7 +305,7 @@ public class CallRedirectionProcessor implements CallRedirectionCallback { * The current rule to decide whether the implemented {@link CallRedirectionService} should * allow interactive responses with users is only based on whether it is in car mode. */ - mAllowInteractiveResponse = !callsManager.getSystemStateHelper().isCarMode(); + mAllowInteractiveResponse = !callsManager.getSystemStateHelper().isCarModeOrProjectionActive(); mCallRedirectionProcessorHelper = new CallRedirectionProcessorHelper( context, callsManager, phoneAccountRegistrar); mProcessedDestinationUri = mCallRedirectionProcessorHelper.formatNumberForRedirection( diff --git a/src/com/android/server/telecom/settings/BlockedNumbersUtil.java b/src/com/android/server/telecom/settings/BlockedNumbersUtil.java index 67634e445..4be75f8e8 100644 --- a/src/com/android/server/telecom/settings/BlockedNumbersUtil.java +++ b/src/com/android/server/telecom/settings/BlockedNumbersUtil.java @@ -95,7 +95,8 @@ public final class BlockedNumbersUtil { if (showNotification) { Intent intent = new Intent(context, CallBlockDisabledActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity( - context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); + context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT + | PendingIntent.FLAG_IMMUTABLE); String title = context.getString( R.string.phone_strings_call_blocking_turned_off_notification_title_txt); diff --git a/src/com/android/server/telecom/ui/IncomingCallNotifier.java b/src/com/android/server/telecom/ui/IncomingCallNotifier.java index edea89bb0..0c1c5a3d9 100644 --- a/src/com/android/server/telecom/ui/IncomingCallNotifier.java +++ b/src/com/android/server/telecom/ui/IncomingCallNotifier.java @@ -282,12 +282,12 @@ public class IncomingCallNotifier extends CallsManagerListenerBase { R.anim.on_going_call, getActionText(R.string.answer_incoming_call, R.color.notification_action_answer), PendingIntent.getBroadcast(mContext, 0, answerIntent, - PendingIntent.FLAG_CANCEL_CURRENT)); + PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE)); builder.addAction( R.drawable.ic_close_dk, getActionText(R.string.decline_incoming_call, R.color.notification_action_decline), PendingIntent.getBroadcast(mContext, 0, rejectIntent, - PendingIntent.FLAG_CANCEL_CURRENT)); + PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE)); return builder; } diff --git a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java index 4e32c9fc9..12d382090 100644 --- a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java +++ b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java @@ -67,18 +67,15 @@ import android.text.TextDirectionHeuristics; import android.text.TextUtils; import android.telecom.CallerInfo; +import android.util.ArrayMap; import java.lang.Override; import java.lang.String; import java.util.ArrayList; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.atomic.AtomicInteger; - -// TODO: Needed for move to system service: import com.android.internal.R; /** * Creates a notification for calls that the user missed (neither answered nor rejected). @@ -139,8 +136,10 @@ public class MissedCallNotifierImpl extends CallsManagerListenerBase implements private final DeviceIdleControllerAdapter mDeviceIdleControllerAdapter; private UserHandle mCurrentUserHandle; + // Used to guard access to mMissedCallCounts + private final Object mMissedCallCountsLock = new Object(); // Used to track the number of missed calls. - private ConcurrentMap<UserHandle, AtomicInteger> mMissedCallCounts; + private final Map<UserHandle, Integer> mMissedCallCounts; private List<UserHandle> mUsersToLoadAfterBootComplete = new ArrayList<>(); @@ -164,7 +163,7 @@ public class MissedCallNotifierImpl extends CallsManagerListenerBase implements mDefaultDialerCache = defaultDialerCache; mNotificationBuilderFactory = notificationBuilderFactory; - mMissedCallCounts = new ConcurrentHashMap<>(); + mMissedCallCounts = new ArrayMap<>(); } /** Clears missed call notification and marks the call log's missed calls as read. */ @@ -263,17 +262,16 @@ public class MissedCallNotifierImpl extends CallsManagerListenerBase implements } private void sendNotificationThroughDefaultDialer(String dialerPackage, CallInfo callInfo, - UserHandle userHandle) { - int count = mMissedCallCounts.get(userHandle).get(); + UserHandle userHandle, int missedCallCount) { Intent intent = getShowMissedCallIntentForDefaultDialer(dialerPackage) .setFlags(Intent.FLAG_RECEIVER_FOREGROUND) .putExtra(TelecomManager.EXTRA_CLEAR_MISSED_CALLS_INTENT, createClearMissedCallsPendingIntent(userHandle)) - .putExtra(TelecomManager.EXTRA_NOTIFICATION_COUNT, count) + .putExtra(TelecomManager.EXTRA_NOTIFICATION_COUNT, missedCallCount) .putExtra(TelecomManager.EXTRA_NOTIFICATION_PHONE_NUMBER, callInfo == null ? null : callInfo.getPhoneNumber()); - if (count == 1 && callInfo != null) { + if (missedCallCount == 1 && callInfo != null) { final Uri handleUri = callInfo.getHandle(); String handle = handleUri == null ? null : handleUri.getSchemeSpecificPart(); @@ -284,8 +282,8 @@ public class MissedCallNotifierImpl extends CallsManagerListenerBase implements } } - - Log.w(this, "Showing missed calls through default dialer."); + Log.i(this, "sendNotificationThroughDefaultDialer; count=%d, dialerPackage=%s", + missedCallCount, intent.getPackage()); Bundle options = exemptFromPowerSavingTemporarily(dialerPackage, userHandle); mContext.sendBroadcastAsUser(intent, userHandle, READ_PHONE_STATE, options); } @@ -293,7 +291,7 @@ public class MissedCallNotifierImpl extends CallsManagerListenerBase implements /** * Create a system notification for the missed call. * - * @param call The missed call. + * @param callInfo The missed call. */ @Override public void showMissedCallNotification(@NonNull CallInfo callInfo) { @@ -311,13 +309,21 @@ public class MissedCallNotifierImpl extends CallsManagerListenerBase implements } private void showMissedCallNotification(@NonNull CallInfo callInfo, UserHandle userHandle) { - Log.i(this, "showMissedCallNotification: userHandle=%d", userHandle.getIdentifier()); - mMissedCallCounts.putIfAbsent(userHandle, new AtomicInteger(0)); - int missCallCounts = mMissedCallCounts.get(userHandle).incrementAndGet(); + int missedCallCounts; + synchronized (mMissedCallCountsLock) { + Integer currentCount = mMissedCallCounts.get(userHandle); + missedCallCounts = currentCount == null ? 0 : currentCount; + missedCallCounts++; + mMissedCallCounts.put(userHandle, missedCallCounts); + } + + Log.i(this, "showMissedCallNotification: userHandle=%d, missedCallCount=%d", + userHandle.getIdentifier(), missedCallCounts); String dialerPackage = getDefaultDialerPackage(userHandle); if (shouldManageNotificationThroughDefaultDialer(dialerPackage, userHandle)) { - sendNotificationThroughDefaultDialer(dialerPackage, callInfo, userHandle); + sendNotificationThroughDefaultDialer(dialerPackage, callInfo, userHandle, + missedCallCounts); return; } @@ -327,7 +333,7 @@ public class MissedCallNotifierImpl extends CallsManagerListenerBase implements // Display the first line of the notification: // 1 missed call: <caller name || handle> // More than 1 missed call: <number of calls> + "missed calls" - if (missCallCounts == 1) { + if (missedCallCounts == 1) { expandedText = getNameForMissedCallNotification(callInfo); CallerInfo ci = callInfo.getCallerInfo(); @@ -339,7 +345,7 @@ public class MissedCallNotifierImpl extends CallsManagerListenerBase implements } else { titleResId = R.string.notification_missedCallsTitle; expandedText = - mContext.getString(R.string.notification_missedCallsMsg, missCallCounts); + mContext.getString(R.string.notification_missedCallsMsg, missedCallCounts); } // Create a public viewable version of the notification, suitable for display when sensitive @@ -381,7 +387,7 @@ public class MissedCallNotifierImpl extends CallsManagerListenerBase implements String handle = callInfo.getHandleSchemeSpecificPart(); // Add additional actions when there is only 1 missed call, like call-back and SMS. - if (missCallCounts == 1) { + if (missedCallCounts == 1) { Log.d(this, "Add actions with number %s.", Log.piiHandle(handle)); if (!TextUtils.isEmpty(handle) @@ -410,7 +416,7 @@ public class MissedCallNotifierImpl extends CallsManagerListenerBase implements } } else { Log.d(this, "Suppress actions. handle: %s, missedCalls: %d.", Log.piiHandle(handle), - missCallCounts); + missedCallCounts); } Notification notification = builder.build(); @@ -430,12 +436,14 @@ public class MissedCallNotifierImpl extends CallsManagerListenerBase implements /** Cancels the "missed call" notification. */ private void cancelMissedCallNotification(UserHandle userHandle) { // Reset the number of missed calls to 0. - mMissedCallCounts.putIfAbsent(userHandle, new AtomicInteger(0)); - mMissedCallCounts.get(userHandle).set(0); + synchronized(mMissedCallCountsLock) { + mMissedCallCounts.put(userHandle, 0); + } String dialerPackage = getDefaultDialerPackage(userHandle); if (shouldManageNotificationThroughDefaultDialer(dialerPackage, userHandle)) { - sendNotificationThroughDefaultDialer(dialerPackage, null, userHandle); + sendNotificationThroughDefaultDialer(dialerPackage, null, userHandle, + 0 /* missedCallCount */); return; } @@ -612,7 +620,9 @@ public class MissedCallNotifierImpl extends CallsManagerListenerBase implements Log.d(MissedCallNotifierImpl.this, "onQueryComplete()..."); if (cursor != null) { try { - mMissedCallCounts.remove(userHandle); + synchronized(mMissedCallCountsLock) { + mMissedCallCounts.remove(userHandle); + } while (cursor.moveToNext()) { // Get data about the missed call from the cursor final String handleString = cursor.getString(CALL_LOG_COLUMN_NUMBER); |
