Merge "Ensure connection video capabilities can only be set if video supported." into nyc-dev
diff --git a/src/com/android/server/telecom/AsyncBlockCheckTask.java b/src/com/android/server/telecom/AsyncBlockCheckTask.java
deleted file mode 100644
index 67a797d..0000000
--- a/src/com/android/server/telecom/AsyncBlockCheckTask.java
+++ /dev/null
@@ -1,129 +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.content.Context;
-import android.os.AsyncTask;
-import android.os.Handler;
-
-import com.android.internal.telephony.BlockChecker;
-
-/**
- * An {@link AsyncTask} that checks if a call needs to be blocked.
- * <p> An {@link AsyncTask} is used to perform the block check to avoid blocking the main thread.
- * The block check itself is performed in the {@link AsyncTask#doInBackground(Object[])}. However
- * a {@link Handler} passed by the caller is used to perform additional possibly intensive
- * operations such as call screening.
- */
-class AsyncBlockCheckTask extends AsyncTask<String, Void, Boolean> {
- private final Context mContext;
- private final Call mIncomingCall;
- private final CallScreening mCallScreening;
- private final boolean mShouldSendToVoicemail;
- private final Handler mHandler = new Handler();
- private final Object mCallScreeningListenerLock = new Object();
-
- private Session mLogSubsession;
- private Runnable mBlockCheckTimeoutRunnable = new Runnable("ABCT.bCTR") {
- @Override
- public void loggedRun() {
- synchronized (mCallScreeningListenerLock) {
- if (mCallScreeningListener != null) {
- timeoutBlockCheck();
- mCallScreeningListener = null;
- }
- }
- }
- };
- private CallScreening.Listener mCallScreeningListener;
-
- AsyncBlockCheckTask(Context context, Call incomingCall, CallScreening callScreening,
- CallScreening.Listener callScreeningListener,
- boolean shouldSendToVoicemail) {
- mContext = context;
- mIncomingCall = incomingCall;
- mCallScreening = callScreening;
- mCallScreeningListener = callScreeningListener;
- mShouldSendToVoicemail = shouldSendToVoicemail;
- }
-
- @Override
- protected void onPreExecute() {
- // This Task will run onPostExecute after the containing session has ended. Add an invisible
- // subsession to keep track of this.
- Log.startSession("ABCT.oPE");
- mLogSubsession = Log.createSubsession();
- mHandler.postDelayed(mBlockCheckTimeoutRunnable.prepare(),
- Timeouts.getBlockCheckTimeoutMillis(mContext.getContentResolver()));
- }
-
- private void timeoutBlockCheck() {
- Log.event(mIncomingCall, Log.Events.BLOCK_CHECK_TIMED_OUT);
- mCallScreeningListener.onCallScreeningCompleted(
- mIncomingCall,
- true /*shouldAllowCall*/,
- false /*shouldReject*/,
- false /*shouldAddToCallLog*/,
- false /*shouldShowNotification*/);
- }
-
- @Override
- protected Boolean doInBackground(String... params) {
- try {
- Log.continueSession(mLogSubsession, "ABCT.DIB");
- Log.event(mIncomingCall, Log.Events.BLOCK_CHECK_INITIATED);
- return BlockChecker.isBlocked(mContext, params[0]);
- } finally {
- Log.endSession();
- }
- }
-
- @Override
- protected void onPostExecute(Boolean isBlocked) {
- synchronized (mCallScreeningListenerLock) {
- mHandler.removeCallbacks(null);
- mBlockCheckTimeoutRunnable.cancel();
- if (mCallScreeningListener != null) {
- processIsBlockedLocked(isBlocked);
- mCallScreeningListener = null;
- }
- }
- // End invisible subsession started in onPreExecute
- Log.endSession();
- }
-
- private void processIsBlockedLocked(boolean isBlocked) {
- Log.event(mIncomingCall, Log.Events.BLOCK_CHECK_FINISHED);
- if (isBlocked) {
- mCallScreeningListener.onCallScreeningCompleted(
- mIncomingCall,
- false /*shouldAllowCall*/,
- true /*shouldReject*/,
- false /*shouldAddToCallLog*/,
- false /*shouldShowNotification*/);
- } else if (mShouldSendToVoicemail) {
- mCallScreeningListener.onCallScreeningCompleted(
- mIncomingCall,
- false /*shouldAllowCall*/,
- true /*shouldReject*/,
- true /*shouldAddToCallLog*/,
- true /*shouldShowNotification*/);
- } else {
- mCallScreening.screenCall();
- }
- }
-}
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index fb61e15..4c70913 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -83,7 +83,7 @@
public interface Listener {
void onSuccessfulOutgoingCall(Call call, int callState);
void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause);
- void onSuccessfulIncomingCall(Call call, boolean shouldSendToVoicemail);
+ void onSuccessfulIncomingCall(Call call);
void onFailedIncomingCall(Call call);
void onSuccessfulUnknownCall(Call call, int callState);
void onFailedUnknownCall(Call call);
@@ -120,7 +120,7 @@
@Override
public void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause) {}
@Override
- public void onSuccessfulIncomingCall(Call call, boolean shouldSendToVoicemail) {}
+ public void onSuccessfulIncomingCall(Call call) {}
@Override
public void onFailedIncomingCall(Call call) {}
@Override
@@ -1080,28 +1080,6 @@
}
}
- private void processDirectToVoicemail() {
- if (mDirectToVoicemailQueryPending) {
- boolean shouldSendToVoicemail;
- if (mCallerInfo != null && mCallerInfo.shouldSendToVoicemail) {
- Log.i(this, "Directing call to voicemail: %s.", this);
- // TODO: Once we move State handling from CallsManager to Call, we
- // will not need to set STATE_RINGING state prior to calling reject.
- shouldSendToVoicemail = true;
- } else {
- shouldSendToVoicemail = false;
- }
- // TODO: Make this class (not CallsManager) responsible for changing
- // the call state to STATE_RINGING.
- // TODO: Replace this with state transition to STATE_RINGING.
- for (Listener l : mListeners) {
- l.onSuccessfulIncomingCall(this, shouldSendToVoicemail);
- }
-
- mDirectToVoicemailQueryPending = false;
- }
- }
-
/**
* Starts the create connection sequence. Upon completion, there should exist an active
* connection through a connection service (or the call will have failed).
@@ -1147,22 +1125,11 @@
switch (mCallDirection) {
case CALL_DIRECTION_INCOMING:
- // We do not handle incoming calls immediately when they are verified by the
- // connection service. We allow the caller-info-query code to execute first so
- // that we can read the direct-to-voicemail property before deciding if we want
- // to show the incoming call to the user or if we want to reject the call.
- mDirectToVoicemailQueryPending = true;
-
- // Timeout the direct-to-voicemail lookup execution so that we dont wait too long
- // before showing the user the incoming call screen.
- mHandler.postDelayed(new Runnable("C.hCCS") {
- @Override
- public void loggedRun() {
- synchronized (mLock) {
- processDirectToVoicemail();
- }
- }
- }.prepare(), Timeouts.getDirectToVoicemailMillis(mContext.getContentResolver()));
+ // Listeners (just CallsManager for now) will be responsible for checking whether
+ // the call should be blocked.
+ for (Listener l : mListeners) {
+ l.onSuccessfulIncomingCall(this);
+ }
break;
case CALL_DIRECTION_OUTGOING:
for (Listener l : mListeners) {
@@ -1795,7 +1762,6 @@
}
}
- processDirectToVoicemail();
Trace.endSection();
}
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 1280950..7bc727c 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -97,8 +97,8 @@
Log.d(LOG_TAG, "Call state changed for TC@%s: %s -> %s", call.getId(),
CallState.toString(oldState), CallState.toString(newState));
- if (mCallStateToCalls.get(oldState) != null) {
- mCallStateToCalls.get(oldState).remove(call);
+ for (int i = 0; i < mCallStateToCalls.size(); i++) {
+ mCallStateToCalls.valueAt(i).remove(call);
}
if (mCallStateToCalls.get(newState) != null) {
mCallStateToCalls.get(newState).add(call);
@@ -157,8 +157,8 @@
Log.d(LOG_TAG, "Call removed with id TC@%s in state %s", call.getId(),
CallState.toString(call.getState()));
- if (mCallStateToCalls.get(call.getState()) != null) {
- mCallStateToCalls.get(call.getState()).remove(call);
+ for (int i = 0; i < mCallStateToCalls.size(); i++) {
+ mCallStateToCalls.valueAt(i).remove(call);
}
updateForegroundCall();
@@ -216,7 +216,7 @@
mCallStateToCalls.get(call.getState()).remove(call);
}
mActiveDialingOrConnectingCalls.add(call);
- mCallAudioModeStateMachine.sendMessage(
+ mCallAudioModeStateMachine.sendMessageWithArgs(
CallAudioModeStateMachine.MT_AUDIO_SPEEDUP_FOR_RINGING_CALL,
makeArgsForModeStateMachine());
}
@@ -267,7 +267,7 @@
if (call != mForegroundCall) {
return;
}
- mCallAudioModeStateMachine.sendMessage(
+ mCallAudioModeStateMachine.sendMessageWithArgs(
CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE,
makeArgsForModeStateMachine());
}
@@ -388,7 +388,8 @@
mRingingCalls.clear();
mRinger.stopRinging();
mRinger.stopCallWaiting();
- mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NO_MORE_RINGING_CALLS,
+ mCallAudioModeStateMachine.sendMessageWithArgs(
+ CallAudioModeStateMachine.NO_MORE_RINGING_CALLS,
makeArgsForModeStateMachine());
}
@@ -451,7 +452,7 @@
@VisibleForTesting
public void setIsTonePlaying(boolean isTonePlaying) {
mIsTonePlaying = isTonePlaying;
- mCallAudioModeStateMachine.sendMessage(
+ mCallAudioModeStateMachine.sendMessageWithArgs(
isTonePlaying ? CallAudioModeStateMachine.TONE_STARTED_PLAYING
: CallAudioModeStateMachine.TONE_STOPPED_PLAYING,
makeArgsForModeStateMachine());
@@ -496,7 +497,7 @@
private void onCallLeavingActiveDialingOrConnecting() {
if (mActiveDialingOrConnectingCalls.size() == 0) {
- mCallAudioModeStateMachine.sendMessage(
+ mCallAudioModeStateMachine.sendMessageWithArgs(
CallAudioModeStateMachine.NO_MORE_ACTIVE_OR_DIALING_CALLS,
makeArgsForModeStateMachine());
}
@@ -504,21 +505,23 @@
private void onCallLeavingRinging() {
if (mRingingCalls.size() == 0) {
- mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NO_MORE_RINGING_CALLS,
+ mCallAudioModeStateMachine.sendMessageWithArgs(
+ CallAudioModeStateMachine.NO_MORE_RINGING_CALLS,
makeArgsForModeStateMachine());
}
}
private void onCallLeavingHold() {
if (mHoldingCalls.size() == 0) {
- mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NO_MORE_HOLDING_CALLS,
+ mCallAudioModeStateMachine.sendMessageWithArgs(
+ CallAudioModeStateMachine.NO_MORE_HOLDING_CALLS,
makeArgsForModeStateMachine());
}
}
private void onCallEnteringActiveDialingOrConnecting() {
if (mActiveDialingOrConnectingCalls.size() == 1) {
- mCallAudioModeStateMachine.sendMessage(
+ mCallAudioModeStateMachine.sendMessageWithArgs(
CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL,
makeArgsForModeStateMachine());
}
@@ -526,14 +529,16 @@
private void onCallEnteringRinging() {
if (mRingingCalls.size() == 1) {
- mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NEW_RINGING_CALL,
+ mCallAudioModeStateMachine.sendMessageWithArgs(
+ CallAudioModeStateMachine.NEW_RINGING_CALL,
makeArgsForModeStateMachine());
}
}
private void onCallEnteringHold() {
if (mHoldingCalls.size() == 1) {
- mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NEW_HOLDING_CALL,
+ mCallAudioModeStateMachine.sendMessageWithArgs(
+ CallAudioModeStateMachine.NEW_HOLDING_CALL,
makeArgsForModeStateMachine());
}
}
@@ -691,4 +696,14 @@
mRinger.stopCallWaiting();
}
}
+
+ @VisibleForTesting
+ public Set<Call> getTrackedCalls() {
+ return mCalls;
+ }
+
+ @VisibleForTesting
+ public SparseArray<LinkedHashSet<Call>> getCallStateToCalls() {
+ return mCallStateToCalls;
+ }
}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/CallAudioModeStateMachine.java b/src/com/android/server/telecom/CallAudioModeStateMachine.java
index 76ddbea..92adca6 100644
--- a/src/com/android/server/telecom/CallAudioModeStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioModeStateMachine.java
@@ -308,6 +308,13 @@
if (args.isTonePlaying) {
mCallAudioManager.stopCallWaiting();
}
+ // If a MT-audio-speedup call gets disconnected by the connection service
+ // concurrently with the user answering it, we may get this message
+ // indicating that a ringing call has disconnected while this state machine
+ // is in the SimCallFocusState.
+ if (!args.hasActiveOrDialingCalls) {
+ transitionTo(destinationStateAfterNoMoreActiveCalls(args));
+ }
return HANDLED;
case NO_MORE_HOLDING_CALLS:
// Do nothing.
@@ -483,6 +490,10 @@
return currentState == null ? "no state" : currentState.getName();
}
+ public void sendMessageWithArgs(int messageCode, MessageArgs args) {
+ sendMessage(messageCode, args);
+ }
+
@Override
protected void onPreHandleMessage(Message msg) {
if (msg.obj != null && msg.obj instanceof MessageArgs) {
diff --git a/src/com/android/server/telecom/CallScreening.java b/src/com/android/server/telecom/CallScreening.java
deleted file mode 100644
index 22201a3..0000000
--- a/src/com/android/server/telecom/CallScreening.java
+++ /dev/null
@@ -1,261 +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.Manifest;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.telecom.DefaultDialerManager;
-import android.telecom.CallScreeningService;
-import android.text.TextUtils;
-
-import com.android.internal.telecom.ICallScreeningAdapter;
-import com.android.internal.telecom.ICallScreeningService;
-
-import java.util.List;
-
-/**
- * Binds to {@link ICallScreeningService} to allow call blocking. A single instance of this class
- * handles a single call.
- */
-public class CallScreening {
- public interface Listener {
- void onCallScreeningCompleted(
- Call call,
- boolean shouldAllowCall,
- boolean shouldReject,
- boolean shouldAddToCallLog,
- boolean shouldShowNotification);
- }
-
- private final Context mContext;
- private final Listener mListener;
- private final TelecomSystem.SyncRoot mLock;
- private final PhoneAccountRegistrar mPhoneAccountRegistrar;
- private final CallsManager mCallsManager;
- private final Handler mHandler = new Handler();
- private Call mCall;
- private ICallScreeningService mService;
- private ServiceConnection mConnection;
-
- public CallScreening(
- Context context,
- CallsManager callsManager,
- TelecomSystem.SyncRoot lock,
- PhoneAccountRegistrar phoneAccountRegistrar,
- Call call) {
- mContext = context;
- mCallsManager = callsManager;
- mListener = callsManager;
- mLock = lock;
- mPhoneAccountRegistrar = phoneAccountRegistrar;
- mCall = call;
- }
-
- public void screenCall() {
- if (!bindService()) {
- Log.d(this, "no service, giving up");
- performCleanup();
- } else {
- mHandler.postDelayed(new Runnable("CS.sC") {
- @Override
- public void loggedRun() {
- synchronized (mLock) {
- Log.event(mCall, Log.Events.SCREENING_TIMED_OUT);
- performCleanup();
- }
- }
- }.prepare(), Timeouts.getCallScreeningTimeoutMillis(mContext.getContentResolver()));
- }
- }
-
- private void performCleanup() {
- if (mCall != null) {
- mListener.onCallScreeningCompleted(mCall, true, false, false, false);
- mCall = null;
- }
- if (mConnection != null) {
- // We still need to call unbind even if the service disconnected.
- mContext.unbindService(mConnection);
- mConnection = null;
- }
- mHandler.removeCallbacksAndMessages(null);
- mService = null;
- }
-
- private boolean bindService() {
- String dialerPackage = DefaultDialerManager
- .getDefaultDialerApplication(mContext, UserHandle.USER_CURRENT);
- if (TextUtils.isEmpty(dialerPackage)) {
- return false;
- }
-
- Intent intent = new Intent(CallScreeningService.SERVICE_INTERFACE)
- .setPackage(dialerPackage);
- List<ResolveInfo> entries = mContext.getPackageManager().queryIntentServicesAsUser(
- intent, 0, mCallsManager.getCurrentUserHandle().getIdentifier());
- if (entries.isEmpty()) {
- return false;
- }
-
- ResolveInfo entry = entries.get(0);
- if (entry.serviceInfo == null) {
- return false;
- }
-
- if (entry.serviceInfo.permission == null || !entry.serviceInfo.permission.equals(
- Manifest.permission.BIND_SCREENING_SERVICE)) {
- Log.w(this, "CallScreeningService must require BIND_SCREENING_SERVICE permission: " +
- entry.serviceInfo.packageName);
- return false;
- }
-
- ComponentName componentName =
- new ComponentName(entry.serviceInfo.packageName, entry.serviceInfo.name);
- Log.event(mCall, Log.Events.BIND_SCREENING, componentName);
- intent.setComponent(componentName);
- ServiceConnection connection = new CallScreeningServiceConnection();
- if (mContext.bindServiceAsUser(
- intent,
- connection,
- Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
- UserHandle.CURRENT)) {
- Log.d(this, "bindService, found service, waiting for it to connect");
- mConnection = connection;
- return true;
- }
-
- return false;
- }
-
- private void onServiceBound(ICallScreeningService service) {
- mService = service;
- try {
- mService.screenCall(new CallScreeningAdapter(), ParcelableCallUtils.toParcelableCall(
- mCall, false /* includeVideoProvider */, mPhoneAccountRegistrar));
- } catch (RemoteException e) {
- Log.e(this, e, "Failed to set the call screening adapter.");
- performCleanup();
- }
- }
-
- private class CallScreeningServiceConnection implements ServiceConnection {
- @Override
- public void onServiceConnected(ComponentName componentName, IBinder service) {
- Log.startSession("CSCR.oSC");
- try {
- synchronized (mLock) {
- Log.event(mCall, Log.Events.SCREENING_BOUND, componentName);
- if (mCall == null) {
- performCleanup();
- } else {
- onServiceBound(ICallScreeningService.Stub.asInterface(service));
- }
- }
- } finally {
- Log.endSession();
- }
- }
-
- @Override
- public void onServiceDisconnected(ComponentName componentName) {
- Log.startSession("CSCR.oSD");
- try {
- synchronized (mLock) {
- performCleanup();
- }
- } finally {
- Log.endSession();
- }
- }
- }
-
- private class CallScreeningAdapter extends ICallScreeningAdapter.Stub {
- @Override
- public void allowCall(String callId) {
- try {
- Log.startSession("CSCR.aC");
- long token = Binder.clearCallingIdentity();
- try {
- synchronized (mLock) {
- Log.d(this, "allowCall(%s)", callId);
- if (mCall != null && mCall.getId().equals(callId)) {
- mListener.onCallScreeningCompleted(
- mCall,
- true /* shouldAllowCall */,
- false /* shouldReject */,
- false /* shouldAddToCallLog */,
- false /* shouldShowNotification */);
- } else {
- Log.w(this, "allowCall, unknown call id: %s", callId);
- }
- mCall = null;
- performCleanup();
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- } finally {
- Log.endSession();
- }
- }
-
- @Override
- public void disallowCall(
- String callId,
- boolean shouldReject,
- boolean shouldAddToCallLog,
- boolean shouldShowNotification) {
- try {
- Log.startSession("CSCR.dC");
- long token = Binder.clearCallingIdentity();
- try {
- synchronized (mLock) {
- Log.i(this, "disallowCall(%s), shouldReject: %b, shouldAddToCallLog: %b, "
- + "shouldShowNotification: %b", callId, shouldReject,
- shouldAddToCallLog, shouldShowNotification);
- if (mCall != null && mCall.getId().equals(callId)) {
- mListener.onCallScreeningCompleted(
- mCall,
- false /* shouldAllowCall */,
- shouldReject,
- shouldAddToCallLog,
- shouldShowNotification);
- } else {
- Log.w(this, "disallowCall, unknown call id: %s", callId);
- }
- mCall = null;
- performCleanup();
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- } finally {
- Log.endSession();
- }
- }
- }
-}
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index ea3a342..913c1f6 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -16,7 +16,6 @@
package com.android.server.telecom;
-import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.UserInfo;
@@ -55,10 +54,17 @@
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.TelephonyProperties;
import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.telecom.CallLogManager.LogCallCompletedListener;
import com.android.server.telecom.TelecomServiceImpl.DefaultDialerManagerAdapter;
+import com.android.server.telecom.callfiltering.AsyncBlockCheckFilter;
+import com.android.server.telecom.callfiltering.BlockCheckerAdapter;
+import com.android.server.telecom.callfiltering.CallFilterResultCallback;
+import com.android.server.telecom.callfiltering.CallFilteringResult;
+import com.android.server.telecom.callfiltering.CallScreeningServiceFilter;
+import com.android.server.telecom.callfiltering.DirectToVoicemailCallFilter;
+import com.android.server.telecom.callfiltering.IncomingCallFilter;
import com.android.server.telecom.components.ErrorDialogActivity;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -78,7 +84,7 @@
*/
@VisibleForTesting
public class CallsManager extends Call.ListenerBase
- implements VideoProviderProxy.Listener, CallScreening.Listener {
+ implements VideoProviderProxy.Listener, CallFilterResultCallback {
// TODO: Consider renaming this CallsManagerPlugin.
@VisibleForTesting
@@ -181,6 +187,8 @@
private final PhoneAccountRegistrar mPhoneAccountRegistrar;
private final MissedCallNotifier mMissedCallNotifier;
private final CallerInfoLookupHelper mCallerInfoLookupHelper;
+ private final DefaultDialerManagerAdapter mDefaultDialerManagerAdapter;
+ private final Timeouts.Adapter mTimeoutsAdapter;
private final Set<Call> mLocallyDisconnectingCalls = new HashSet<>();
private final Set<Call> mPendingCallsToDisconnect = new HashSet<>();
/* Handler tied to thread in which CallManager was initialized. */
@@ -210,6 +218,7 @@
WiredHeadsetManager wiredHeadsetManager,
SystemStateProvider systemStateProvider,
DefaultDialerManagerAdapter defaultDialerAdapter,
+ Timeouts.Adapter timeoutsAdapter,
AsyncRingtonePlayer asyncRingtonePlayer) {
mContext = context;
mLock = lock;
@@ -220,7 +229,9 @@
StatusBarNotifier statusBarNotifier = new StatusBarNotifier(context, this);
mWiredHeadsetManager = wiredHeadsetManager;
mBluetoothManager = bluetoothManager;
+ mDefaultDialerManagerAdapter = defaultDialerAdapter;
mDockManager = new DockManager(context);
+ mTimeoutsAdapter = timeoutsAdapter;
mCallerInfoLookupHelper = new CallerInfoLookupHelper(context, mCallerInfoAsyncQueryFactory,
mContactsAsyncHelper, mLock);
@@ -329,62 +340,56 @@
}
@Override
- public void onSuccessfulIncomingCall(Call incomingCall, boolean shouldSendToVoicemail) {
+ public void onSuccessfulIncomingCall(Call incomingCall) {
Log.d(this, "onSuccessfulIncomingCall");
-
- // TODO: Parallelize Call screening, block check, and send to voicemail.
- final String number = incomingCall.getHandle() == null ? null : incomingCall.getHandle()
- .getSchemeSpecificPart();
- Log.v(this, "Looking up information for: %s.", Log.piiHandle(number));
-
- new AsyncBlockCheckTask(mContext, incomingCall,
- new CallScreening(mContext, CallsManager.this, mLock,
- mPhoneAccountRegistrar, incomingCall), this, shouldSendToVoicemail)
- .execute(number);
+ List<IncomingCallFilter.CallFilter> filters = new ArrayList<>();
+ filters.add(new DirectToVoicemailCallFilter(mCallerInfoLookupHelper));
+ filters.add(new AsyncBlockCheckFilter(mContext, new BlockCheckerAdapter()));
+ filters.add(new CallScreeningServiceFilter(mContext, this, mPhoneAccountRegistrar,
+ mDefaultDialerManagerAdapter,
+ new ParcelableCallUtils.Converter(), mLock));
+ new IncomingCallFilter(mContext, this, incomingCall, mLock,
+ mTimeoutsAdapter, filters).performFiltering();
}
@Override
- public void onCallScreeningCompleted(
- Call incomingCall,
- boolean shouldAllowCall,
- boolean shouldReject,
- boolean shouldAddToCallLog,
- boolean shouldShowNotification) {
+ public void onCallFilteringComplete(Call incomingCall, CallFilteringResult result) {
// Only set the incoming call as ringing if it isn't already disconnected. It is possible
// that the connection service disconnected the call before it was even added to Telecom, in
// which case it makes no sense to set it back to a ringing state.
if (incomingCall.getState() != CallState.DISCONNECTED &&
incomingCall.getState() != CallState.DISCONNECTING) {
setCallState(incomingCall, CallState.RINGING,
- shouldAllowCall ? "successful incoming call" : "blocking call");
+ result.shouldAllowCall ? "successful incoming call" : "blocking call");
} else {
- Log.i(this, "onCallScreeningCompleted: call already disconnected.");
+ Log.i(this, "onCallFilteringCompleted: call already disconnected.");
}
- if (shouldAllowCall) {
+ if (result.shouldAllowCall) {
if (hasMaximumRingingCalls()) {
- Log.i(this, "onCallScreeningCompleted: Call rejected! Exceeds maximum number of " +
+ Log.i(this, "onCallFilteringCompleted: Call rejected! Exceeds maximum number of " +
"ringing calls.");
rejectCallAndLog(incomingCall);
} else if (hasMaximumDialingCalls()) {
- Log.i(this, "onCallScreeningCompleted: Call rejected! Exceeds maximum number of " +
+ Log.i(this, "onCallFilteringCompleted: Call rejected! Exceeds maximum number of " +
"dialing calls.");
rejectCallAndLog(incomingCall);
} else {
addCall(incomingCall);
}
} else {
- if (shouldReject) {
- Log.i(this, "onCallScreeningCompleted: blocked call, rejecting.");
+ if (result.shouldReject) {
+ Log.i(this, "onCallFilteringCompleted: blocked call, rejecting.");
incomingCall.reject(false, null);
}
- if (shouldAddToCallLog) {
+ if (result.shouldAddToCallLog) {
Log.i(this, "onCallScreeningCompleted: blocked call, adding to call log.");
- if (shouldShowNotification) {
+ if (result.shouldShowNotification) {
Log.w(this, "onCallScreeningCompleted: blocked call, showing notification.");
}
- mCallLogManager.logCall(incomingCall, Calls.MISSED_TYPE, shouldShowNotification);
- } else if (shouldShowNotification) {
+ mCallLogManager.logCall(incomingCall, Calls.MISSED_TYPE,
+ result.shouldShowNotification);
+ } else if (result.shouldShowNotification) {
Log.i(this, "onCallScreeningCompleted: blocked call, showing notification.");
mMissedCallNotifier.showMissedCallNotification(incomingCall);
}
diff --git a/src/com/android/server/telecom/DtmfLocalTonePlayer.java b/src/com/android/server/telecom/DtmfLocalTonePlayer.java
index 20c9dd8..64c0c55 100644
--- a/src/com/android/server/telecom/DtmfLocalTonePlayer.java
+++ b/src/com/android/server/telecom/DtmfLocalTonePlayer.java
@@ -34,7 +34,7 @@
* class employs a concept of a call "session" that starts and stops when the foreground call
* changes.
*/
-class DtmfLocalTonePlayer {
+public class DtmfLocalTonePlayer {
/** Generator used to actually play the tone. */
private ToneGenerator mToneGenerator;
diff --git a/src/com/android/server/telecom/Log.java b/src/com/android/server/telecom/Log.java
index d4cb77a..fda21fd 100644
--- a/src/com/android/server/telecom/Log.java
+++ b/src/com/android/server/telecom/Log.java
@@ -97,10 +97,14 @@
public static final String BIND_SCREENING = "BIND_SCREENING";
public static final String SCREENING_BOUND = "SCREENING_BOUND";
public static final String SCREENING_SENT = "SCREENING_SENT";
- public static final String SCREENING_TIMED_OUT = "SCREENING_TIMED_OUT";
+ public static final String SCREENING_COMPLETED = "SCREENING_COMPLETED";
public static final String BLOCK_CHECK_INITIATED = "BLOCK_CHECK_INITIATED";
- public static final String BLOCK_CHECK_TIMED_OUT = "BLOCK_CHECK_TIMED_OUT";
public static final String BLOCK_CHECK_FINISHED = "BLOCK_CHECK_FINISHED";
+ public static final String DIRECT_TO_VM_INITIATED = "DIRECT_TO_VM_INITIATED";
+ public static final String DIRECT_TO_VM_FINISHED = "DIRECT_TO_VM_FINISHED";
+ public static final String FILTERING_INITIATED = "FILTERING_INITIATED";
+ public static final String FILTERING_COMPLETED = "FILTERING_COMPLETED";
+ public static final String FILTERING_TIMED_OUT = "FILTERING_TIMED_OUT";
public static final String REMOTELY_HELD = "REMOTELY_HELD";
public static final String REMOTELY_UNHELD = "REMOTELY_UNHELD";
public static final String PULL = "PULL";
@@ -121,8 +125,10 @@
put(REQUEST_UNHOLD, SET_ACTIVE);
put(START_CONNECTION, SET_DIALING);
put(BIND_CS, CS_BOUND);
- put(SCREENING_SENT, SCREENING_TIMED_OUT);
- put(BLOCK_CHECK_INITIATED, BLOCK_CHECK_TIMED_OUT);
+ put(SCREENING_SENT, SCREENING_COMPLETED);
+ put(BLOCK_CHECK_INITIATED, BLOCK_CHECK_FINISHED);
+ put(DIRECT_TO_VM_INITIATED, DIRECT_TO_VM_FINISHED);
+ put(FILTERING_INITIATED, FILTERING_COMPLETED);
}};
}
diff --git a/src/com/android/server/telecom/ParcelableCallUtils.java b/src/com/android/server/telecom/ParcelableCallUtils.java
index 28b74e2..c1c1c57 100644
--- a/src/com/android/server/telecom/ParcelableCallUtils.java
+++ b/src/com/android/server/telecom/ParcelableCallUtils.java
@@ -28,6 +28,14 @@
* Utilities dealing with {@link ParcelableCall}.
*/
public class ParcelableCallUtils {
+ public static class Converter {
+ public ParcelableCall toParcelableCall(Call call, boolean includeVideoProvider,
+ PhoneAccountRegistrar phoneAccountRegistrar) {
+ return ParcelableCallUtils.toParcelableCall(
+ call, includeVideoProvider, phoneAccountRegistrar);
+ }
+ }
+
/**
* Parcels all information for a {@link Call} into a new {@link ParcelableCall} instance.
*
diff --git a/src/com/android/server/telecom/RingbackPlayer.java b/src/com/android/server/telecom/RingbackPlayer.java
index af60b68..bfaf8a2 100644
--- a/src/com/android/server/telecom/RingbackPlayer.java
+++ b/src/com/android/server/telecom/RingbackPlayer.java
@@ -24,7 +24,7 @@
* able to turn off and on as the user switches between calls. This is why it is implemented as its
* own class.
*/
-class RingbackPlayer {
+public class RingbackPlayer {
private final InCallTonePlayer.Factory mPlayerFactory;
diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java
index 8a2b41c..77b1590 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -31,7 +31,7 @@
* Controls the ringtone player.
*/
@VisibleForTesting
-public final class Ringer {
+public class Ringer {
private static final long[] VIBRATION_PATTERN = new long[] {
0, // No delay before starting
1000, // How long to vibrate
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index 9da8a24..304f13b 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -153,6 +153,7 @@
AudioServiceFactory audioServiceFactory,
BluetoothPhoneServiceImplFactory
bluetoothPhoneServiceImplFactory,
+ Timeouts.Adapter timeoutsAdapter,
AsyncRingtonePlayer asyncRingtonePlayer) {
mContext = context.getApplicationContext();
Log.setContext(mContext);
@@ -194,6 +195,7 @@
wiredHeadsetManager,
systemStateProvider,
defaultDialerAdapter,
+ timeoutsAdapter,
asyncRingtonePlayer);
mRespondViaSmsManager = new RespondViaSmsManager(mCallsManager, mLock);
diff --git a/src/com/android/server/telecom/Timeouts.java b/src/com/android/server/telecom/Timeouts.java
index 88a3a06..08d7410 100644
--- a/src/com/android/server/telecom/Timeouts.java
+++ b/src/com/android/server/telecom/Timeouts.java
@@ -26,7 +26,7 @@
* These methods are safe to call from any thread, including the UI thread.
*/
public final class Timeouts {
- public class Adapter {
+ public static class Adapter {
public Adapter() { }
public long getCallScreeningTimeoutMillis(ContentResolver cr) {
diff --git a/src/com/android/server/telecom/callfiltering/AsyncBlockCheckFilter.java b/src/com/android/server/telecom/callfiltering/AsyncBlockCheckFilter.java
new file mode 100644
index 0000000..8872297
--- /dev/null
+++ b/src/com/android/server/telecom/callfiltering/AsyncBlockCheckFilter.java
@@ -0,0 +1,90 @@
+/*
+ * 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.callfiltering;
+
+import android.content.Context;
+import android.os.AsyncTask;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.Log;
+import com.android.server.telecom.Session;
+
+/**
+ * An {@link AsyncTask} that checks if a call needs to be blocked.
+ * <p> An {@link AsyncTask} is used to perform the block check to avoid blocking the main thread.
+ * The block check itself is performed in the {@link AsyncTask#doInBackground(Object[])}.
+ */
+public class AsyncBlockCheckFilter extends AsyncTask<String, Void, Boolean>
+ implements IncomingCallFilter.CallFilter {
+ private final Context mContext;
+ private final BlockCheckerAdapter mBlockCheckerAdapter;
+ private Call mIncomingCall;
+ private Session mLogSubsession;
+ private CallFilterResultCallback mCallback;
+
+ public AsyncBlockCheckFilter(Context context, BlockCheckerAdapter blockCheckerAdapter) {
+ mContext = context;
+ mBlockCheckerAdapter = blockCheckerAdapter;
+ }
+
+ @Override
+ public void startFilterLookup(Call call, CallFilterResultCallback callback) {
+ mCallback = callback;
+ mIncomingCall = call;
+ String number = call.getHandle() == null ?
+ null : call.getHandle().getSchemeSpecificPart();
+ this.execute(number);
+ }
+
+ @Override
+ protected void onPreExecute() {
+ mLogSubsession = Log.createSubsession();
+ }
+
+ @Override
+ protected Boolean doInBackground(String... params) {
+ try {
+ Log.continueSession(mLogSubsession, "ABCF.dIB");
+ Log.event(mIncomingCall, Log.Events.BLOCK_CHECK_INITIATED);
+ return mBlockCheckerAdapter.isBlocked(mContext, params[0]);
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Boolean isBlocked) {
+ CallFilteringResult result;
+ if (isBlocked) {
+ result = new CallFilteringResult(
+ false, // shouldAllowCall
+ true, //shouldReject
+ false, //shouldAddToCallLog
+ false // shouldShowNotification
+ );
+ } else {
+ result = new CallFilteringResult(
+ true, // shouldAllowCall
+ false, // shouldReject
+ true, // shouldAddToCallLog
+ true // shouldShowNotification
+ );
+ }
+ Log.event(mIncomingCall, Log.Events.BLOCK_CHECK_FINISHED, result);
+ mCallback.onCallFilteringComplete(mIncomingCall, result);
+ }
+}
diff --git a/src/com/android/server/telecom/callfiltering/BlockCheckerAdapter.java b/src/com/android/server/telecom/callfiltering/BlockCheckerAdapter.java
new file mode 100644
index 0000000..f15a507
--- /dev/null
+++ b/src/com/android/server/telecom/callfiltering/BlockCheckerAdapter.java
@@ -0,0 +1,29 @@
+/*
+ * 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.callfiltering;
+
+import android.content.Context;
+
+import com.android.internal.telephony.BlockChecker;
+
+public class BlockCheckerAdapter {
+ public BlockCheckerAdapter() { }
+
+ public boolean isBlocked(Context context, String number) {
+ return BlockChecker.isBlocked(context, number);
+ }
+}
diff --git a/src/com/android/server/telecom/callfiltering/CallFilteringResult.java b/src/com/android/server/telecom/callfiltering/CallFilteringResult.java
index 88d707c..9e35d86 100644
--- a/src/com/android/server/telecom/callfiltering/CallFilteringResult.java
+++ b/src/com/android/server/telecom/callfiltering/CallFilteringResult.java
@@ -67,4 +67,28 @@
result = 31 * result + (shouldShowNotification ? 1 : 0);
return result;
}
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("[");
+ if (shouldAllowCall) {
+ sb.append("Allow");
+ } else if (shouldReject) {
+ sb.append("Reject");
+ } else {
+ sb.append("Ignore");
+ }
+
+ if (shouldAddToCallLog) {
+ sb.append(", logged");
+ }
+
+ if (shouldShowNotification) {
+ sb.append(", notified");
+ }
+ sb.append("]");
+
+ return sb.toString();
+ }
}
diff --git a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
new file mode 100644
index 0000000..db99e2f
--- /dev/null
+++ b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
@@ -0,0 +1,262 @@
+/*
+ * 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.callfiltering;
+
+import android.Manifest;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ResolveInfo;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.telecom.CallScreeningService;
+import android.text.TextUtils;
+
+import com.android.internal.telecom.ICallScreeningAdapter;
+import com.android.internal.telecom.ICallScreeningService;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.Log;
+import com.android.server.telecom.ParcelableCallUtils;
+import com.android.server.telecom.PhoneAccountRegistrar;
+import com.android.server.telecom.TelecomServiceImpl;
+import com.android.server.telecom.TelecomSystem;
+
+import java.util.List;
+
+/**
+ * Binds to {@link ICallScreeningService} to allow call blocking. A single instance of this class
+ * handles a single call.
+ */
+public class CallScreeningServiceFilter implements IncomingCallFilter.CallFilter {
+ private class CallScreeningServiceConnection implements ServiceConnection {
+ @Override
+ public void onServiceConnected(ComponentName componentName, IBinder service) {
+ Log.startSession("CSCR.oSC");
+ try {
+ synchronized (mTelecomLock) {
+ Log.event(mCall, Log.Events.SCREENING_BOUND, componentName);
+ if (!mHasFinished) {
+ onServiceBound(ICallScreeningService.Stub.asInterface(service));
+ }
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName componentName) {
+ Log.startSession("CSCR.oSD");
+ try {
+ synchronized (mTelecomLock) {
+ finishCallScreening();
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
+ }
+
+ private class CallScreeningAdapter extends ICallScreeningAdapter.Stub {
+ @Override
+ public void allowCall(String callId) {
+ Log.startSession("CSCR.aC");
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mTelecomLock) {
+ Log.d(this, "allowCall(%s)", callId);
+ if (mCall != null && mCall.getId().equals(callId)) {
+ mResult = new CallFilteringResult(
+ true, // shouldAllowCall
+ false, //shouldReject
+ true, //shouldAddToCallLog
+ true // shouldShowNotification
+ );
+ } else {
+ Log.w(this, "allowCall, unknown call id: %s", callId);
+ }
+ finishCallScreening();
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ Log.endSession();
+ }
+ }
+
+ @Override
+ public void disallowCall(
+ String callId,
+ boolean shouldReject,
+ boolean shouldAddToCallLog,
+ boolean shouldShowNotification) {
+ Log.startSession("CSCR.dC");
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mTelecomLock) {
+ Log.i(this, "disallowCall(%s), shouldReject: %b, shouldAddToCallLog: %b, "
+ + "shouldShowNotification: %b", callId, shouldReject,
+ shouldAddToCallLog, shouldShowNotification);
+ if (mCall != null && mCall.getId().equals(callId)) {
+ mResult = new CallFilteringResult(
+ false, // shouldAllowCall
+ shouldReject, //shouldReject
+ shouldAddToCallLog, //shouldAddToCallLog
+ shouldShowNotification // shouldShowNotification
+ );
+ } else {
+ Log.w(this, "disallowCall, unknown call id: %s", callId);
+ }
+ finishCallScreening();
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ Log.endSession();
+ }
+ }
+ }
+
+ private final Context mContext;
+ private final PhoneAccountRegistrar mPhoneAccountRegistrar;
+ private final CallsManager mCallsManager;
+ private final TelecomServiceImpl.DefaultDialerManagerAdapter mDefaultDialerManagerAdapter;
+ private final ParcelableCallUtils.Converter mParcelableCallUtilsConverter;
+ private final TelecomSystem.SyncRoot mTelecomLock;
+
+ private Call mCall;
+ private CallFilterResultCallback mCallback;
+ private ICallScreeningService mService;
+ private ServiceConnection mConnection;
+
+ private boolean mHasFinished = false;
+ private CallFilteringResult mResult = new CallFilteringResult(
+ true, // shouldAllowCall
+ false, //shouldReject
+ true, //shouldAddToCallLog
+ true // shouldShowNotification
+ );
+
+ public CallScreeningServiceFilter(
+ Context context,
+ CallsManager callsManager,
+ PhoneAccountRegistrar phoneAccountRegistrar,
+ TelecomServiceImpl.DefaultDialerManagerAdapter defaultDialerManagerAdapter,
+ ParcelableCallUtils.Converter parcelableCallUtilsConverter,
+ TelecomSystem.SyncRoot lock) {
+ mContext = context;
+ mPhoneAccountRegistrar = phoneAccountRegistrar;
+ mCallsManager = callsManager;
+ mDefaultDialerManagerAdapter = defaultDialerManagerAdapter;
+ mParcelableCallUtilsConverter = parcelableCallUtilsConverter;
+ mTelecomLock = lock;
+ }
+
+ @Override
+ public void startFilterLookup(Call call, CallFilterResultCallback callback) {
+ if (mHasFinished) {
+ Log.w(this, "Attempting to reuse CallScreeningServiceFilter. Ignoring.");
+ return;
+ }
+ Log.event(call, Log.Events.SCREENING_SENT);
+ mCall = call;
+ mCallback = callback;
+ if (!bindService()) {
+ Log.i(this, "Could not bind to call screening service");
+ finishCallScreening();
+ }
+ }
+
+ private void finishCallScreening() {
+ if (!mHasFinished) {
+ Log.event(mCall, Log.Events.SCREENING_COMPLETED, mResult);
+ mCallback.onCallFilteringComplete(mCall, mResult);
+
+ if (mConnection != null) {
+ // We still need to call unbind even if the service disconnected.
+ mContext.unbindService(mConnection);
+ mConnection = null;
+ }
+ mService = null;
+ mHasFinished = true;
+ }
+ }
+
+ private boolean bindService() {
+ String dialerPackage = mDefaultDialerManagerAdapter
+ .getDefaultDialerApplication(mContext, UserHandle.USER_CURRENT);
+ if (TextUtils.isEmpty(dialerPackage)) {
+ Log.i(this, "Default dialer is empty. Not performing call screening.");
+ return false;
+ }
+
+ Intent intent = new Intent(CallScreeningService.SERVICE_INTERFACE)
+ .setPackage(dialerPackage);
+ List<ResolveInfo> entries = mContext.getPackageManager().queryIntentServicesAsUser(
+ intent, 0, mCallsManager.getCurrentUserHandle().getIdentifier());
+ if (entries.isEmpty()) {
+ Log.i(this, "There are no call screening services installed on this device.");
+ return false;
+ }
+
+ ResolveInfo entry = entries.get(0);
+ if (entry.serviceInfo == null) {
+ Log.w(this, "The call screening service has invalid service info");
+ return false;
+ }
+
+ if (entry.serviceInfo.permission == null || !entry.serviceInfo.permission.equals(
+ Manifest.permission.BIND_SCREENING_SERVICE)) {
+ Log.w(this, "CallScreeningService must require BIND_SCREENING_SERVICE permission: " +
+ entry.serviceInfo.packageName);
+ return false;
+ }
+
+ ComponentName componentName =
+ new ComponentName(entry.serviceInfo.packageName, entry.serviceInfo.name);
+ Log.event(mCall, Log.Events.BIND_SCREENING, componentName);
+ intent.setComponent(componentName);
+ ServiceConnection connection = new CallScreeningServiceConnection();
+ if (mContext.bindServiceAsUser(
+ intent,
+ connection,
+ Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
+ UserHandle.CURRENT)) {
+ Log.d(this, "bindService, found service, waiting for it to connect");
+ mConnection = connection;
+ return true;
+ }
+
+ return false;
+ }
+
+ private void onServiceBound(ICallScreeningService service) {
+ mService = service;
+ try {
+ mService.screenCall(new CallScreeningAdapter(),
+ mParcelableCallUtilsConverter.toParcelableCall(
+ mCall,
+ false, /* includeVideoProvider */
+ mPhoneAccountRegistrar));
+ } catch (RemoteException e) {
+ Log.e(this, e, "Failed to set the call screening adapter.");
+ finishCallScreening();
+ }
+ }
+}
diff --git a/src/com/android/server/telecom/callfiltering/DirectToVoicemailCallFilter.java b/src/com/android/server/telecom/callfiltering/DirectToVoicemailCallFilter.java
index 8163cfd..1363d62 100644
--- a/src/com/android/server/telecom/callfiltering/DirectToVoicemailCallFilter.java
+++ b/src/com/android/server/telecom/callfiltering/DirectToVoicemailCallFilter.java
@@ -32,29 +32,31 @@
@Override
public void startFilterLookup(final Call call, CallFilterResultCallback callback) {
+ Log.event(call, Log.Events.DIRECT_TO_VM_INITIATED);
final Uri callHandle = call.getHandle();
mCallerInfoLookupHelper.startLookup(callHandle,
new CallerInfoLookupHelper.OnQueryCompleteListener() {
@Override
public void onCallerInfoQueryComplete(Uri handle, CallerInfo info) {
+ CallFilteringResult result;
if (callHandle.equals(handle)) {
if (info.shouldSendToVoicemail) {
- callback.onCallFilteringComplete(call,
- new CallFilteringResult(
- false, // shouldAllowCall
- true, // shouldReject
- true, // shouldAddToCallLog
- true // shouldShowNotification
- ));
+ result = new CallFilteringResult(
+ false, // shouldAllowCall
+ true, // shouldReject
+ true, // shouldAddToCallLog
+ true // shouldShowNotification
+ );
} else {
- callback.onCallFilteringComplete(call,
- new CallFilteringResult(
- true, // shouldAllowCall
- false, // shouldReject
- true, // shouldAddToCallLog
- true // shouldShowNotification
- ));
+ result = new CallFilteringResult(
+ true, // shouldAllowCall
+ false, // shouldReject
+ true, // shouldAddToCallLog
+ true // shouldShowNotification
+ );
}
+ Log.event(call, Log.Events.DIRECT_TO_VM_FINISHED, result);
+ callback.onCallFilteringComplete(call, result);
} else {
Log.w(this, "CallerInfo lookup returned with a different handle than " +
"what was passed in. Was %s, should be %s", handle, callHandle);
diff --git a/src/com/android/server/telecom/callfiltering/IncomingCallFilter.java b/src/com/android/server/telecom/callfiltering/IncomingCallFilter.java
index 0a046b6..4685ec0 100644
--- a/src/com/android/server/telecom/callfiltering/IncomingCallFilter.java
+++ b/src/com/android/server/telecom/callfiltering/IncomingCallFilter.java
@@ -35,7 +35,7 @@
void startFilterLookup(Call call, CallFilterResultCallback listener);
}
- private final TelecomSystem.SyncRoot mLock;
+ private final TelecomSystem.SyncRoot mTelecomLock;
private final Context mContext;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final List<CallFilter> mFilters;
@@ -59,22 +59,25 @@
mContext = context;
mListener = listener;
mCall = call;
- mLock = lock;
+ mTelecomLock = lock;
mFilters = filters;
mNumPendingFilters = filters.size();
mTimeoutsAdapter = timeoutsAdapter;
}
public void performFiltering() {
+ Log.event(mCall, Log.Events.FILTERING_INITIATED);
for (CallFilter filter : mFilters) {
filter.startFilterLookup(mCall, this);
}
mHandler.postDelayed(new Runnable("ICF.pFTO") { // performFiltering time-out
@Override
public void loggedRun() {
- synchronized (mLock) { // synchronized to prevent a race on mResult
+ // synchronized to prevent a race on mResult and to enter into Telecom.
+ synchronized (mTelecomLock) {
if (mIsPending) {
Log.i(IncomingCallFilter.this, "Call filtering has timed out.");
+ Log.event(mCall, Log.Events.FILTERING_TIMED_OUT);
mListener.onCallFilteringComplete(mCall, mResult);
mIsPending = false;
}
@@ -84,16 +87,20 @@
}
public void onCallFilteringComplete(Call call, CallFilteringResult result) {
- synchronized (mLock) {
+ synchronized (mTelecomLock) { // synchronizing to prevent race on mResult
mNumPendingFilters--;
mResult = result.combine(mResult);
if (mNumPendingFilters == 0) {
mHandler.post(new Runnable("ICF.oCFC") {
@Override
public void loggedRun() {
- if (mIsPending) {
- mListener.onCallFilteringComplete(mCall, mResult);
- mIsPending = false;
+ // synchronized to enter into Telecom.
+ synchronized (mTelecomLock) {
+ if (mIsPending) {
+ Log.event(mCall, Log.Events.FILTERING_COMPLETED, mResult);
+ mListener.onCallFilteringComplete(mCall, mResult);
+ mIsPending = false;
+ }
}
}
}.prepare());
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index 38b0b81..bef8453 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -42,6 +42,7 @@
import com.android.server.telecom.ProximitySensorManager;
import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.TelecomWakeLock;
+import com.android.server.telecom.Timeouts;
import com.android.server.telecom.ui.MissedCallNotifierImpl;
/**
@@ -146,6 +147,7 @@
phoneAccountRegistrar);
}
},
+ new Timeouts.Adapter(),
new AsyncRingtonePlayer()
));
}
diff --git a/tests/src/com/android/server/telecom/tests/AsyncBlockCheckFilterTest.java b/tests/src/com/android/server/telecom/tests/AsyncBlockCheckFilterTest.java
new file mode 100644
index 0000000..79d711b
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/AsyncBlockCheckFilterTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.tests;
+
+import android.content.Context;
+import android.net.Uri;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.callfiltering.AsyncBlockCheckFilter;
+import com.android.server.telecom.callfiltering.BlockCheckerAdapter;
+import com.android.server.telecom.callfiltering.CallFilterResultCallback;
+import com.android.server.telecom.callfiltering.CallFilteringResult;
+
+import org.mockito.Mock;
+
+import java.util.concurrent.CountDownLatch;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class AsyncBlockCheckFilterTest extends TelecomTestCase {
+ @Mock private Context mContext;
+ @Mock private BlockCheckerAdapter mBlockCheckerAdapter;
+ @Mock private Call mCall;
+ @Mock private CallFilterResultCallback mCallback;
+
+ private AsyncBlockCheckFilter mFilter;
+ private static final CallFilteringResult BLOCK_RESULT = new CallFilteringResult(
+ false, // shouldAllowCall
+ true, //shouldReject
+ false, //shouldAddToCallLog
+ false // shouldShowNotification
+ );
+
+ private static final CallFilteringResult PASS_RESULT = new CallFilteringResult(
+ true, // shouldAllowCall
+ false, // shouldReject
+ true, // shouldAddToCallLog
+ true // shouldShowNotification
+ );
+
+ private static final Uri TEST_HANDLE = Uri.parse("tel:1235551234");
+ private static final int TEST_TIMEOUT = 100;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ when(mCall.getHandle()).thenReturn(TEST_HANDLE);
+ mFilter = new AsyncBlockCheckFilter(mContext, mBlockCheckerAdapter);
+ }
+
+ @SmallTest
+ public void testBlockNumber() {
+ final CountDownLatch latch = new CountDownLatch(1);
+ doAnswer(invocation -> {
+ latch.countDown();
+ return true;
+ }).when(mBlockCheckerAdapter)
+ .isBlocked(any(Context.class), eq(TEST_HANDLE.getSchemeSpecificPart()));
+ mFilter.startFilterLookup(mCall, mCallback);
+ waitOnLatch(latch);
+ verify(mCallback, timeout(TEST_TIMEOUT))
+ .onCallFilteringComplete(eq(mCall), eq(BLOCK_RESULT));
+ }
+
+ @SmallTest
+ public void testDontBlockNumber() {
+ final CountDownLatch latch = new CountDownLatch(1);
+ doAnswer(invocation -> {
+ latch.countDown();
+ return false;
+ }).when(mBlockCheckerAdapter)
+ .isBlocked(any(Context.class), eq(TEST_HANDLE.getSchemeSpecificPart()));
+ mFilter.startFilterLookup(mCall, mCallback);
+ waitOnLatch(latch);
+ verify(mCallback, timeout(TEST_TIMEOUT))
+ .onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
+ }
+
+ private void waitOnLatch(CountDownLatch latch) {
+ while (latch.getCount() > 0) {
+ try {
+ latch.await();
+ } catch (InterruptedException e) {
+ // do nothing
+ }
+ }
+ }
+}
diff --git a/tests/src/com/android/server/telecom/tests/BasicCallTests.java b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
index 8bee217..11a5624 100644
--- a/tests/src/com/android/server/telecom/tests/BasicCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
@@ -27,11 +27,14 @@
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.IContentProvider;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
import android.os.Process;
import android.provider.BlockedNumberContract;
import android.telecom.Call;
@@ -285,6 +288,7 @@
mTelecomSystem.getTelecomServiceImpl().getBinder()
.addNewIncomingCall(mPhoneAccountA0.getAccountHandle(), extras);
+ waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
verify(mConnectionServiceFixtureA.getTestDouble())
.createConnection(any(PhoneAccountHandle.class), anyString(),
any(ConnectionRequest.class), eq(true), eq(false));
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java b/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
new file mode 100644
index 0000000..34325f0
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
@@ -0,0 +1,282 @@
+/*
+ * 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.tests;
+
+import android.media.ToneGenerator;
+import android.telecom.DisconnectCause;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.util.SparseArray;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallAudioModeStateMachine;
+import com.android.server.telecom.CallAudioRouteStateMachine;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.CallAudioManager;
+import com.android.server.telecom.DtmfLocalTonePlayer;
+import com.android.server.telecom.InCallTonePlayer;
+import com.android.server.telecom.RingbackPlayer;
+import com.android.server.telecom.Ringer;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.util.LinkedHashSet;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class CallAudioManagerTest extends TelecomTestCase {
+ @Mock private CallAudioRouteStateMachine mCallAudioRouteStateMachine;
+ @Mock private CallsManager mCallsManager;
+ @Mock private CallAudioModeStateMachine mCallAudioModeStateMachine;
+ @Mock private InCallTonePlayer.Factory mPlayerFactory;
+ @Mock private Ringer mRinger;
+ @Mock private RingbackPlayer mRingbackPlayer;
+ @Mock private DtmfLocalTonePlayer mDtmfLocalTonePlayer;
+
+ private CallAudioManager mCallAudioManager;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ doAnswer((invocation) -> {
+ InCallTonePlayer mockInCallTonePlayer = mock(InCallTonePlayer.class);
+ doAnswer((invocation2) -> {
+ mCallAudioManager.setIsTonePlaying(true);
+ return null;
+ }).when(mockInCallTonePlayer).startTone();
+ return mockInCallTonePlayer;
+ }).when(mPlayerFactory).createPlayer(anyInt());
+ mCallAudioManager = new CallAudioManager(
+ mCallAudioRouteStateMachine,
+ mCallsManager,
+ mCallAudioModeStateMachine,
+ mPlayerFactory,
+ mRinger,
+ mRingbackPlayer,
+ mDtmfLocalTonePlayer);
+ }
+
+ @MediumTest
+ public void testSingleIncomingCallFlowWithoutMTSpeedUp() {
+ Call call = createIncomingCall();
+ when(call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO))
+ .thenReturn(false);
+
+ ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
+ ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
+ // Answer the incoming call
+ mCallAudioManager.onIncomingCallAnswered(call);
+ when(call.getState()).thenReturn(CallState.ACTIVE);
+ mCallAudioManager.onCallStateChanged(call, CallState.RINGING, CallState.ACTIVE);
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.NO_MORE_RINGING_CALLS), captor.capture());
+ CallAudioModeStateMachine.MessageArgs correctArgs =
+ new CallAudioModeStateMachine.MessageArgs(
+ true, // hasActiveOrDialingCalls
+ false, // hasRingingCalls
+ false, // hasHoldingCalls
+ false, // isTonePlaying
+ false, // foregroundCallIsVoip
+ null // session
+ );
+ assertMessageArgEquality(correctArgs, captor.getValue());
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
+ assertMessageArgEquality(correctArgs, captor.getValue());
+
+ disconnectCall(call);
+ stopTone();
+
+ mCallAudioManager.onCallRemoved(call);
+ verifyProperCleanup();
+ }
+
+ @MediumTest
+ public void testSingleIncomingCallFlowWithMTSpeedUp() {
+ Call call = createIncomingCall();
+ when(call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO))
+ .thenReturn(true);
+
+ ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
+ ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
+ // Answer the incoming call
+ mCallAudioManager.onIncomingCallAnswered(call);
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.MT_AUDIO_SPEEDUP_FOR_RINGING_CALL), captor.capture());
+ CallAudioModeStateMachine.MessageArgs correctArgs =
+ new CallAudioModeStateMachine.MessageArgs(
+ true, // hasActiveOrDialingCalls
+ false, // hasRingingCalls
+ false, // hasHoldingCalls
+ false, // isTonePlaying
+ false, // foregroundCallIsVoip
+ null // session
+ );
+ assertMessageArgEquality(correctArgs, captor.getValue());
+ assertMessageArgEquality(correctArgs, captor.getValue());
+ when(call.getState()).thenReturn(CallState.ACTIVE);
+ mCallAudioManager.onCallStateChanged(call, CallState.RINGING, CallState.ACTIVE);
+
+ disconnectCall(call);
+ stopTone();
+
+ mCallAudioManager.onCallRemoved(call);
+ verifyProperCleanup();
+ }
+
+ @MediumTest
+ public void testSingleOutgoingCall() {
+ Call call = mock(Call.class);
+ when(call.getState()).thenReturn(CallState.CONNECTING);
+
+ mCallAudioManager.onCallAdded(call);
+ assertEquals(call, mCallAudioManager.getForegroundCall());
+ ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
+ ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
+ verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo(
+ CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
+ CallAudioModeStateMachine.MessageArgs expectedArgs =
+ new CallAudioModeStateMachine.MessageArgs(
+ true, // hasActiveOrDialingCalls
+ false, // hasRingingCalls
+ false, // hasHoldingCalls
+ false, // isTonePlaying
+ false, // foregroundCallIsVoip
+ null // session
+ );
+ assertMessageArgEquality(expectedArgs, captor.getValue());
+
+ when(call.getState()).thenReturn(CallState.DIALING);
+ mCallAudioManager.onCallStateChanged(call, CallState.CONNECTING, CallState.DIALING);
+ verify(mCallAudioModeStateMachine, times(2)).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
+ assertMessageArgEquality(expectedArgs, captor.getValue());
+ verify(mCallAudioModeStateMachine, times(2)).sendMessageWithArgs(
+ anyInt(), any(CallAudioModeStateMachine.MessageArgs.class));
+
+
+ when(call.getState()).thenReturn(CallState.ACTIVE);
+ mCallAudioManager.onCallStateChanged(call, CallState.DIALING, CallState.ACTIVE);
+ verify(mCallAudioModeStateMachine, times(3)).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
+ assertMessageArgEquality(expectedArgs, captor.getValue());
+ verify(mCallAudioModeStateMachine, times(3)).sendMessageWithArgs(
+ anyInt(), any(CallAudioModeStateMachine.MessageArgs.class));
+
+ disconnectCall(call);
+ stopTone();
+
+ mCallAudioManager.onCallRemoved(call);
+ verifyProperCleanup();
+ }
+
+ private Call createIncomingCall() {
+ Call call = mock(Call.class);
+ when(call.getState()).thenReturn(CallState.RINGING);
+
+ mCallAudioManager.onCallAdded(call);
+ assertEquals(call, mCallAudioManager.getForegroundCall());
+ ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
+ ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
+ verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo(
+ CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.NEW_RINGING_CALL), captor.capture());
+ assertMessageArgEquality(new CallAudioModeStateMachine.MessageArgs(
+ false, // hasActiveOrDialingCalls
+ true, // hasRingingCalls
+ false, // hasHoldingCalls
+ false, // isTonePlaying
+ false, // foregroundCallIsVoip
+ null // session
+ ), captor.getValue());
+
+ return call;
+ }
+
+ private void disconnectCall(Call call) {
+ ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
+ ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
+ CallAudioModeStateMachine.MessageArgs correctArgs;
+
+ when(call.getState()).thenReturn(CallState.DISCONNECTED);
+ when(call.getDisconnectCause()).thenReturn(new DisconnectCause(DisconnectCause.LOCAL,
+ "", "", "", ToneGenerator.TONE_PROP_PROMPT));
+
+ mCallAudioManager.onCallStateChanged(call, CallState.ACTIVE, CallState.DISCONNECTED);
+ verify(mPlayerFactory).createPlayer(InCallTonePlayer.TONE_CALL_ENDED);
+ correctArgs = new CallAudioModeStateMachine.MessageArgs(
+ false, // hasActiveOrDialingCalls
+ false, // hasRingingCalls
+ false, // hasHoldingCalls
+ true, // isTonePlaying
+ false, // foregroundCallIsVoip
+ null // session
+ );
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.NO_MORE_ACTIVE_OR_DIALING_CALLS), captor.capture());
+ assertMessageArgEquality(correctArgs, captor.getValue());
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.TONE_STARTED_PLAYING), captor.capture());
+ assertMessageArgEquality(correctArgs, captor.getValue());
+ }
+
+ private void stopTone() {
+ ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
+ ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
+ mCallAudioManager.setIsTonePlaying(false);
+ CallAudioModeStateMachine.MessageArgs correctArgs =
+ new CallAudioModeStateMachine.MessageArgs(
+ false, // hasActiveOrDialingCalls
+ false, // hasRingingCalls
+ false, // hasHoldingCalls
+ false, // isTonePlaying
+ false, // foregroundCallIsVoip
+ null // session
+ );
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.TONE_STOPPED_PLAYING), captor.capture());
+ assertMessageArgEquality(correctArgs, captor.getValue());
+ }
+
+ private void verifyProperCleanup() {
+ assertEquals(0, mCallAudioManager.getTrackedCalls().size());
+ SparseArray<LinkedHashSet<Call>> callStateToCalls = mCallAudioManager.getCallStateToCalls();
+ for (int i = 0; i < callStateToCalls.size(); i++) {
+ assertEquals(0, callStateToCalls.valueAt(i).size());
+ }
+ }
+
+ private void assertMessageArgEquality(CallAudioModeStateMachine.MessageArgs expected,
+ CallAudioModeStateMachine.MessageArgs actual) {
+ assertEquals(expected.hasActiveOrDialingCalls, actual.hasActiveOrDialingCalls);
+ assertEquals(expected.hasHoldingCalls, actual.hasHoldingCalls);
+ assertEquals(expected.hasRingingCalls, actual.hasRingingCalls);
+ assertEquals(expected.isTonePlaying, actual.isTonePlaying);
+ assertEquals(expected.foregroundCallIsVoip, actual.foregroundCallIsVoip);
+ }
+}
diff --git a/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java b/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java
new file mode 100644
index 0000000..3d3306d
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java
@@ -0,0 +1,220 @@
+/*
+ * 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.tests;
+import android.Manifest;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.telecom.CallScreeningService;
+import android.telecom.ParcelableCall;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.internal.telecom.ICallScreeningAdapter;
+import com.android.internal.telecom.ICallScreeningService;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.ParcelableCallUtils;
+import com.android.server.telecom.PhoneAccountRegistrar;
+import com.android.server.telecom.TelecomServiceImpl;
+import com.android.server.telecom.callfiltering.CallFilterResultCallback;
+import com.android.server.telecom.callfiltering.CallFilteringResult;
+import com.android.server.telecom.callfiltering.CallScreeningServiceFilter;
+import com.android.server.telecom.TelecomSystem;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.util.Collections;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class CallScreeningServiceFilterTest extends TelecomTestCase {
+ @Mock Context mContext;
+ @Mock CallsManager mCallsManager;
+ @Mock PhoneAccountRegistrar mPhoneAccountRegistrar;
+ @Mock TelecomServiceImpl.DefaultDialerManagerAdapter mDefaultDialerManagerAdapter;
+ @Mock
+ ParcelableCallUtils.Converter mParcelableCallUtilsConverter;
+ private TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
+
+ @Mock Call mCall;
+ @Mock CallFilterResultCallback mCallback;
+
+ @Mock PackageManager mPackageManager;
+ @Mock IBinder mBinder;
+ @Mock ICallScreeningService mCallScreeningService;
+
+ private static final String PKG_NAME = "com.android.services.telecom.tests";
+ private static final String CLS_NAME = "CallScreeningService";
+ private static final ComponentName COMPONENT_NAME = new ComponentName(PKG_NAME, CLS_NAME);
+ private static final String CALL_ID = "u89prgt9ps78y5";
+
+ private ResolveInfo mResolveInfo;
+
+ private static final CallFilteringResult PASS_RESULT = new CallFilteringResult(
+ true, // shouldAllowCall
+ false, // shouldReject
+ true, // shouldAddToCallLog
+ true // shouldShowNotification
+ );
+
+ private CallScreeningServiceFilter mFilter;
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ when(mCallsManager.getCurrentUserHandle()).thenReturn(UserHandle.CURRENT);
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ when(mCall.getId()).thenReturn(CALL_ID);
+// when(mBinder.queryLocalInterface(anyString())).thenReturn(mCallScreeningService);
+ doReturn(mCallScreeningService).when(mBinder).queryLocalInterface(anyString());
+
+ mResolveInfo = new ResolveInfo() {{
+ serviceInfo = new ServiceInfo();
+ serviceInfo.packageName = PKG_NAME;
+ serviceInfo.name = CLS_NAME;
+ serviceInfo.permission = Manifest.permission.BIND_SCREENING_SERVICE;
+ }};
+
+ mFilter = new CallScreeningServiceFilter(mContext, mCallsManager, mPhoneAccountRegistrar,
+ mDefaultDialerManagerAdapter, mParcelableCallUtilsConverter, mLock);
+
+ when(mDefaultDialerManagerAdapter.getDefaultDialerApplication(
+ eq(mContext), eq(UserHandle.USER_CURRENT))).thenReturn(PKG_NAME);
+ when(mPackageManager.queryIntentServicesAsUser(any(Intent.class), anyInt(), anyInt()))
+ .thenReturn(Collections.singletonList(mResolveInfo));
+ when(mParcelableCallUtilsConverter.toParcelableCall(
+ eq(mCall), anyBoolean(), eq(mPhoneAccountRegistrar))).thenReturn(null);
+ when(mContext.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class),
+ anyInt(), eq(UserHandle.CURRENT))).thenReturn(true);
+ }
+
+ @SmallTest
+ public void testNoDefaultDialer() {
+ when(mDefaultDialerManagerAdapter.getDefaultDialerApplication(
+ eq(mContext), eq(UserHandle.USER_CURRENT))).thenReturn(null);
+ mFilter.startFilterLookup(mCall, mCallback);
+ verify(mCallback).onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
+ }
+
+ @SmallTest
+ public void testNoResolveEntries() {
+ when(mPackageManager.queryIntentServicesAsUser(any(Intent.class), anyInt(), anyInt()))
+ .thenReturn(Collections.emptyList());
+ mFilter.startFilterLookup(mCall, mCallback);
+ verify(mCallback).onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
+ }
+
+ @SmallTest
+ public void testBadResolveEntry() {
+ mResolveInfo.serviceInfo = null;
+ mFilter.startFilterLookup(mCall, mCallback);
+ verify(mCallback).onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
+ }
+
+ @SmallTest
+ public void testPermissionlessFilterService() {
+ mResolveInfo.serviceInfo.permission = null;
+ mFilter.startFilterLookup(mCall, mCallback);
+ verify(mCallback).onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
+ }
+
+ @SmallTest
+ public void testContextFailToBind() {
+ when(mContext.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class),
+ anyInt(), eq(UserHandle.CURRENT))).thenReturn(false);
+ mFilter.startFilterLookup(mCall, mCallback);
+ verify(mCallback).onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
+ }
+
+ @SmallTest
+ public void testExceptionInScreeningService() throws Exception {
+ doThrow(new RemoteException()).when(mCallScreeningService).screenCall(
+ any(ICallScreeningAdapter.class), any(ParcelableCall.class));
+ mFilter.startFilterLookup(mCall, mCallback);
+ ServiceConnection serviceConnection = verifyBindingIntent();
+ serviceConnection.onServiceConnected(COMPONENT_NAME, mBinder);
+ verify(mCallback).onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
+ }
+
+ @SmallTest
+ public void testAllowCall() throws Exception {
+ mFilter.startFilterLookup(mCall, mCallback);
+ ServiceConnection serviceConnection = verifyBindingIntent();
+ serviceConnection.onServiceConnected(COMPONENT_NAME, mBinder);
+ ICallScreeningAdapter csAdapter = getCallScreeningAdapter();
+ csAdapter.allowCall(CALL_ID);
+ verify(mCallback).onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
+ }
+
+ @SmallTest
+ public void testDisallowCall() throws Exception {
+ mFilter.startFilterLookup(mCall, mCallback);
+ ServiceConnection serviceConnection = verifyBindingIntent();
+ serviceConnection.onServiceConnected(COMPONENT_NAME, mBinder);
+ ICallScreeningAdapter csAdapter = getCallScreeningAdapter();
+ csAdapter.disallowCall(CALL_ID,
+ true, // shouldReject
+ false, // shouldAddToCallLog
+ true // shouldShowNotification
+ );
+ verify(mCallback).onCallFilteringComplete(eq(mCall), eq(new CallFilteringResult(
+ false, // shouldAllowCall
+ true, // shouldReject
+ false, // shouldAddToCallLog
+ true // shouldShowNotification
+ )));
+ }
+
+ private ServiceConnection verifyBindingIntent() {
+ ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+ ArgumentCaptor<ServiceConnection> serviceCaptor =
+ ArgumentCaptor.forClass(ServiceConnection.class);
+ verify(mContext).bindServiceAsUser(intentCaptor.capture(), serviceCaptor.capture(),
+ eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
+ eq(UserHandle.CURRENT));
+
+ Intent capturedIntent = intentCaptor.getValue();
+ assertEquals(CallScreeningService.SERVICE_INTERFACE, capturedIntent.getAction());
+ assertEquals(PKG_NAME, capturedIntent.getPackage());
+ assertEquals(COMPONENT_NAME, capturedIntent.getComponent());
+
+ return serviceCaptor.getValue();
+ }
+
+ private ICallScreeningAdapter getCallScreeningAdapter() throws Exception {
+ ArgumentCaptor<ICallScreeningAdapter> captor =
+ ArgumentCaptor.forClass(ICallScreeningAdapter.class);
+ verify(mCallScreeningService).screenCall(captor.capture(), any(ParcelableCall.class));
+ return captor.getValue();
+ }
+}
diff --git a/tests/src/com/android/server/telecom/tests/IncomingCallFilterTest.java b/tests/src/com/android/server/telecom/tests/IncomingCallFilterTest.java
index e766786..6861ad5 100644
--- a/tests/src/com/android/server/telecom/tests/IncomingCallFilterTest.java
+++ b/tests/src/com/android/server/telecom/tests/IncomingCallFilterTest.java
@@ -157,6 +157,13 @@
any(CallFilteringResult.class));
}
+ @SmallTest
+ public void testToString() {
+ assertEquals("[Allow, logged, notified]", RESULT1.toString());
+ assertEquals("[Reject, notified]", RESULT2.toString());
+ assertEquals("[Reject, logged]", RESULT3.toString());
+ }
+
private void setTimeoutLength(long length) throws Exception {
when(mTimeoutsAdapter.getCallScreeningTimeoutMillis(any(ContentResolver.class)))
.thenReturn(length);
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index 655b68f..cee5882 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -31,9 +31,11 @@
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.IContentProvider;
import android.content.Intent;
@@ -70,6 +72,7 @@
import com.android.server.telecom.ProximitySensorManager;
import com.android.server.telecom.ProximitySensorManagerFactory;
import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.Timeouts;
import com.android.server.telecom.components.UserCallIntentProcessor;
import com.android.server.telecom.ui.MissedCallNotifierImpl.MissedCallNotifierImplFactory;
@@ -204,6 +207,7 @@
ConnectionServiceFixture mConnectionServiceFixtureA;
ConnectionServiceFixture mConnectionServiceFixtureB;
+ Timeouts.Adapter mTimeoutsAdapter;
CallerInfoAsyncQueryFactoryFixture mCallerInfoAsyncQueryFactoryFixture;
@@ -290,6 +294,10 @@
mCallerInfoAsyncQueryFactoryFixture = new CallerInfoAsyncQueryFactoryFixture();
+ mTimeoutsAdapter = mock(Timeouts.Adapter.class);
+ when(mTimeoutsAdapter.getCallScreeningTimeoutMillis(any(ContentResolver.class)))
+ .thenReturn(TEST_TIMEOUT / 10L);
+
mTelecomSystem = new TelecomSystem(
mComponentContextFixture.getTestDouble(),
new MissedCallNotifierImplFactory() {
@@ -317,6 +325,7 @@
return mBluetoothPhoneServiceImpl;
}
},
+ mTimeoutsAdapter,
mAsyncRingtonePlayer);
mComponentContextFixture.setTelecomManager(new TelecomManager(