Add new public API to allow call screening
This CL adds a new service that the default dialer can implement to
screen incoming calls.
If the service is implemented then Telecom uses it to decide if a call
should be shown to the user or rejected.
Note, I initially tried to simply extend InCallService instead of
creating a whole new service. The problem with this approach is that:
- this breaks some in call services which show UI as soon as they are
bound to
- the in call service lifecycle is tightly coupled to adding and
removing calls. Call screening happens before a call is added which
makes it a bad fit to in call service
- call screening is a function of only the default dialer's in call
service but the existing code also binds to "controller" call
services
For these reasons it seemed simpler to just create a new optional
service.
BUG: 22857261
Change-Id: I53f2ef93360e0af19b6ce45af21026be6cf3a7f3
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 3859294..de902025 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -265,6 +265,7 @@
// Next PROPERTY value: 0x00000040
//******************************************************************************************
+ private final String mTelecomCallId;
private final Uri mHandle;
private final int mHandlePresentation;
private final String mCallerDisplayName;
@@ -414,6 +415,11 @@
return builder.toString();
}
+ /** {@hide} */
+ public String getTelecomCallId() {
+ return mTelecomCallId;
+ }
+
/**
* @return The handle (e.g., phone number) to which the {@code Call} is currently
* connected.
@@ -567,6 +573,7 @@
/** {@hide} */
public Details(
+ String telecomCallId,
Uri handle,
int handlePresentation,
String callerDisplayName,
@@ -581,6 +588,7 @@
StatusHints statusHints,
Bundle extras,
Bundle intentExtras) {
+ mTelecomCallId = telecomCallId;
mHandle = handle;
mHandlePresentation = handlePresentation;
mCallerDisplayName = callerDisplayName;
@@ -596,6 +604,26 @@
mExtras = extras;
mIntentExtras = intentExtras;
}
+
+ /** {@hide} */
+ public static Details createFromParcelableCall(ParcelableCall parcelableCall) {
+ return new Details(
+ parcelableCall.getId(),
+ parcelableCall.getHandle(),
+ parcelableCall.getHandlePresentation(),
+ parcelableCall.getCallerDisplayName(),
+ parcelableCall.getCallerDisplayNamePresentation(),
+ parcelableCall.getAccountHandle(),
+ parcelableCall.getCapabilities(),
+ parcelableCall.getProperties(),
+ parcelableCall.getDisconnectCause(),
+ parcelableCall.getConnectTimeMillis(),
+ parcelableCall.getGatewayInfo(),
+ parcelableCall.getVideoState(),
+ parcelableCall.getStatusHints(),
+ parcelableCall.getExtras(),
+ parcelableCall.getIntentExtras());
+ }
}
public static abstract class Callback {
@@ -1022,21 +1050,7 @@
/** {@hide} */
final void internalUpdate(ParcelableCall parcelableCall, Map<String, Call> callIdMap) {
// First, we update the internal state as far as possible before firing any updates.
- Details details = new Details(
- parcelableCall.getHandle(),
- parcelableCall.getHandlePresentation(),
- parcelableCall.getCallerDisplayName(),
- parcelableCall.getCallerDisplayNamePresentation(),
- parcelableCall.getAccountHandle(),
- parcelableCall.getCapabilities(),
- parcelableCall.getProperties(),
- parcelableCall.getDisconnectCause(),
- parcelableCall.getConnectTimeMillis(),
- parcelableCall.getGatewayInfo(),
- parcelableCall.getVideoState(),
- parcelableCall.getStatusHints(),
- parcelableCall.getExtras(),
- parcelableCall.getIntentExtras());
+ Details details = Details.createFromParcelableCall(parcelableCall);
boolean detailsChanged = !Objects.equals(mDetails, details);
if (detailsChanged) {
mDetails = details;
diff --git a/telecomm/java/android/telecom/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java
new file mode 100644
index 0000000..1b6e162
--- /dev/null
+++ b/telecomm/java/android/telecom/CallScreeningService.java
@@ -0,0 +1,239 @@
+/*
+ * 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 android.telecom;
+
+import android.annotation.SdkConstant;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+
+import com.android.internal.os.SomeArgs;
+import com.android.internal.telecom.ICallScreeningService;
+import com.android.internal.telecom.ICallScreeningAdapter;
+
+/**
+ * This service can be implemented by the default dialer (see
+ * {@link TelecomManager#getDefaultDialerPackage()}) to allow or disallow incoming calls before
+ * they are shown to a user.
+ * <p>
+ * Below is an example manifest registration for a {@code CallScreeningService}.
+ * <pre>
+ * {@code
+ * <service android:name="your.package.YourCallScreeningServiceImplementation"
+ * android:permission="android.permission.BIND_SCREENING_SERVICE">
+ * <intent-filter>
+ * <action android:name="android.telecom.CallScreeningService"/>
+ * </intent-filter>
+ * </service>
+ * }
+ * </pre>
+ */
+public abstract class CallScreeningService extends Service {
+ /**
+ * The {@link Intent} that must be declared as handled by the service.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE = "android.telecom.CallScreeningService";
+
+ private static final int MSG_SCREEN_CALL = 1;
+
+ private final Handler mHandler = new Handler(Looper.getMainLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SCREEN_CALL:
+ SomeArgs args = (SomeArgs) msg.obj;
+ try {
+ mCallScreeningAdapter = (ICallScreeningAdapter) args.arg1;
+ onScreenCall(
+ Call.Details.createFromParcelableCall((ParcelableCall) args.arg2));
+ } finally {
+ args.recycle();
+ }
+ break;
+ }
+ }
+ };
+
+ private final class CallScreeningBinder extends ICallScreeningService.Stub {
+ @Override
+ public void screenCall(ICallScreeningAdapter adapter, ParcelableCall call) {
+ Log.v(this, "screenCall");
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = adapter;
+ args.arg2 = call;
+ mHandler.obtainMessage(MSG_SCREEN_CALL, args).sendToTarget();
+ }
+ }
+
+ private ICallScreeningAdapter mCallScreeningAdapter;
+
+ /*
+ * Information about how to respond to an incoming call.
+ */
+ public class CallResponse {
+ private final boolean mShouldDisallowCall;
+ private final boolean mShouldRejectCall;
+ private final boolean mShouldSkipCallLog;
+ private final boolean mShouldSkipNotification;
+
+ private CallResponse(
+ boolean shouldDisallowCall,
+ boolean shouldRejectCall,
+ boolean shouldSkipCallLog,
+ boolean shouldSkipNotification) {
+ if (!shouldDisallowCall
+ && (shouldRejectCall || shouldSkipCallLog || shouldSkipNotification)) {
+ throw new IllegalStateException("Invalid response state for allowed call.");
+ }
+
+ mShouldDisallowCall = shouldDisallowCall;
+ mShouldRejectCall = shouldRejectCall;
+ mShouldSkipCallLog = shouldSkipCallLog;
+ mShouldSkipNotification = shouldSkipNotification;
+ }
+
+ /*
+ * @return Whether the incoming call should be blocked.
+ */
+ public boolean getDisallowCall() {
+ return mShouldDisallowCall;
+ }
+
+ /*
+ * @return Whether the incoming call should be disconnected as if the user had manually
+ * rejected it.
+ */
+ public boolean getRejectCall() {
+ return mShouldRejectCall;
+ }
+
+ /*
+ * @return Whether the incoming call should not be displayed in the call log.
+ */
+ public boolean getSkipCallLog() {
+ return mShouldSkipCallLog;
+ }
+
+ /*
+ * @return Whether a missed call notification should not be shown for the incoming call.
+ */
+ public boolean getSkipNotification() {
+ return mShouldSkipNotification;
+ }
+
+ public class Builder {
+ private boolean mShouldDisallowCall;
+ private boolean mShouldRejectCall;
+ private boolean mShouldSkipCallLog;
+ private boolean mShouldSkipNotification;
+
+ /*
+ * Sets whether the incoming call should be blocked.
+ */
+ public Builder setDisallowCall(boolean shouldDisallowCall) {
+ mShouldDisallowCall = shouldDisallowCall;
+ return this;
+ }
+
+ /*
+ * Sets whether the incoming call should be disconnected as if the user had manually
+ * rejected it. This property should only be set to true if the call is disallowed.
+ */
+ public Builder setRejectCall(boolean shouldRejectCall) {
+ mShouldRejectCall = shouldRejectCall;
+ return this;
+ }
+
+ /*
+ * Sets whether the incoming call should not be displayed in the call log. This property
+ * should only be set to true if the call is disallowed.
+ */
+ public Builder setSkipCallLog(boolean shouldSkipCallLog) {
+ mShouldSkipCallLog = shouldSkipCallLog;
+ return this;
+ }
+
+ /*
+ * Sets whether a missed call notification should not be shown for the incoming call.
+ * This property should only be set to true if the call is disallowed.
+ */
+ public Builder setSkipNotification(boolean shouldSkipNotification) {
+ mShouldSkipNotification = shouldSkipNotification;
+ return this;
+ }
+
+ public CallResponse build() {
+ return new CallResponse(
+ mShouldDisallowCall,
+ mShouldRejectCall,
+ mShouldSkipCallLog,
+ mShouldSkipNotification);
+ }
+ }
+ }
+
+ public CallScreeningService() {
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ Log.v(this, "onBind");
+ return new CallScreeningBinder();
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ Log.v(this, "onUnbind");
+ return false;
+ }
+
+ /**
+ * Called when a new incoming call is added.
+ * {@link CallScreeningService#respondToCall(Call.Details, CallScreeningService.CallResponse)}
+ * should be called to allow or disallow the call.
+ *
+ * @param callDetails Information about a new incoming call, see {@link Call.Details}.
+ */
+ public abstract void onScreenCall(Call.Details callDetails);
+
+ /**
+ * Responds to the given call, either allowing it or disallowing it.
+ *
+ * @param callDetails The call to allow.
+ * @param response The {@link CallScreeningService.CallResponse} which contains information
+ * about how to respond to a call.
+ */
+ public final void respondToCall(Call.Details callDetails, CallResponse response) {
+ try {
+ if (response.getDisallowCall()) {
+ mCallScreeningAdapter.disallowCall(
+ callDetails.getTelecomCallId(),
+ response.getRejectCall(),
+ !response.getSkipCallLog(),
+ !response.getSkipNotification());
+ } else {
+ mCallScreeningAdapter.allowCall(callDetails.getTelecomCallId());
+ }
+ } catch (RemoteException e) {
+ }
+ }
+}