From 5efe99753969b0f4991e05830c4f6cc26bd1c2e1 Mon Sep 17 00:00:00 2001 From: Hall Liu Date: Thu, 4 Feb 2021 13:09:45 -0800 Subject: [PATCH 1/2] Refactor CallScreeningService's internal structure Use a single AIDL method and perform logic in Telecom instead of slicing up the call response on the client side. Also pass through the call response to the connection service. Bug: 179412110 Test: atest ConnectionServiceTest CallScreeningServiceTest Change-Id: I878c0ce34142da104dc0e2795487b03a6bdacb5f --- core/api/system-current.txt | 4 +- .../android/telecom/CallScreeningService.aidl | 22 +++ .../android/telecom/CallScreeningService.java | 160 ++++++++++++++++-- telecomm/java/android/telecom/Connection.java | 11 +- .../android/telecom/ConnectionService.java | 25 ++- .../android/telecom/RemoteConnection.java | 12 +- .../telecom/ICallScreeningAdapter.aidl | 15 +- .../internal/telecom/IConnectionService.aidl | 4 +- 8 files changed, 215 insertions(+), 38 deletions(-) create mode 100644 telecomm/java/android/telecom/CallScreeningService.aidl diff --git a/core/api/system-current.txt b/core/api/system-current.txt index bea4b39257649..880f5db4a6751 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -10664,7 +10664,7 @@ package android.telecom { method @Nullable public android.telecom.PhoneAccountHandle getPhoneAccountHandle(); method @Nullable public final String getTelecomCallId(); method @Deprecated public void onAudioStateChanged(android.telecom.AudioState); - method @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public void onCallFilteringCompleted(boolean, boolean); + method @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public void onCallFilteringCompleted(boolean, boolean, @Nullable android.telecom.CallScreeningService.CallResponse, boolean); method public final void resetConnectionTime(); method public void setCallDirection(int); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public final void setConnectTimeMillis(@IntRange(from=0) long); @@ -10841,7 +10841,7 @@ package android.telecom { } public final class RemoteConnection { - method @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public void onCallFilteringCompleted(boolean, boolean); + method @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public void onCallFilteringCompleted(boolean, boolean, @Nullable android.telecom.CallScreeningService.CallResponse, boolean); method @Deprecated public void setAudioState(android.telecom.AudioState); } diff --git a/telecomm/java/android/telecom/CallScreeningService.aidl b/telecomm/java/android/telecom/CallScreeningService.aidl new file mode 100644 index 0000000000000..87b5138745f22 --- /dev/null +++ b/telecomm/java/android/telecom/CallScreeningService.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package android.telecom; + +/** + * {@hide} + */ +parcelable CallScreeningService.ParcelableCallResponse; diff --git a/telecomm/java/android/telecom/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java index 7988b036ccd3a..b7f59e113372b 100644 --- a/telecomm/java/android/telecom/CallScreeningService.java +++ b/telecomm/java/android/telecom/CallScreeningService.java @@ -30,12 +30,16 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.Parcel; +import android.os.Parcelable; import android.os.RemoteException; import com.android.internal.os.SomeArgs; import com.android.internal.telecom.ICallScreeningAdapter; import com.android.internal.telecom.ICallScreeningService; +import java.util.Objects; + /** * This service can be implemented by the default dialer (see * {@link TelecomManager#getDefaultDialerPackage()}) or a third party app to allow or disallow @@ -132,7 +136,10 @@ public abstract class CallScreeningService extends Service { .createFromParcelableCall((ParcelableCall) args.arg2); onScreenCall(callDetails); if (callDetails.getCallDirection() == Call.Details.DIRECTION_OUTGOING) { - mCallScreeningAdapter.allowCall(callDetails.getTelecomCallId()); + mCallScreeningAdapter.onScreeningResponse( + callDetails.getTelecomCallId(), + new ComponentName(getPackageName(), getClass().getName()), + null); } } catch (RemoteException e) { Log.w(this, "Exception when screening call: " + e); @@ -157,6 +164,106 @@ public abstract class CallScreeningService extends Service { private ICallScreeningAdapter mCallScreeningAdapter; + /** + * Parcelable version of {@link CallResponse} used to do IPC. + * @hide + */ + public static class ParcelableCallResponse implements Parcelable { + private final boolean mShouldDisallowCall; + private final boolean mShouldRejectCall; + private final boolean mShouldSilenceCall; + private final boolean mShouldSkipCallLog; + private final boolean mShouldSkipNotification; + private final boolean mShouldScreenCallViaAudioProcessing; + + private ParcelableCallResponse( + boolean shouldDisallowCall, + boolean shouldRejectCall, + boolean shouldSilenceCall, + boolean shouldSkipCallLog, + boolean shouldSkipNotification, + boolean shouldScreenCallViaAudioProcessing) { + mShouldDisallowCall = shouldDisallowCall; + mShouldRejectCall = shouldRejectCall; + mShouldSilenceCall = shouldSilenceCall; + mShouldSkipCallLog = shouldSkipCallLog; + mShouldSkipNotification = shouldSkipNotification; + mShouldScreenCallViaAudioProcessing = shouldScreenCallViaAudioProcessing; + } + + protected ParcelableCallResponse(Parcel in) { + mShouldDisallowCall = in.readBoolean(); + mShouldRejectCall = in.readBoolean(); + mShouldSilenceCall = in.readBoolean(); + mShouldSkipCallLog = in.readBoolean(); + mShouldSkipNotification = in.readBoolean(); + mShouldScreenCallViaAudioProcessing = in.readBoolean(); + } + + public CallResponse toCallResponse() { + return new CallResponse.Builder() + .setDisallowCall(mShouldDisallowCall) + .setRejectCall(mShouldRejectCall) + .setSilenceCall(mShouldSilenceCall) + .setSkipCallLog(mShouldSkipCallLog) + .setSkipNotification(mShouldSkipNotification) + .setShouldScreenCallViaAudioProcessing(mShouldScreenCallViaAudioProcessing) + .build(); + } + + public boolean shouldDisallowCall() { + return mShouldDisallowCall; + } + + public boolean shouldRejectCall() { + return mShouldRejectCall; + } + + public boolean shouldSilenceCall() { + return mShouldSilenceCall; + } + + public boolean shouldSkipCallLog() { + return mShouldSkipCallLog; + } + + public boolean shouldSkipNotification() { + return mShouldSkipNotification; + } + + public boolean shouldScreenCallViaAudioProcessing() { + return mShouldScreenCallViaAudioProcessing; + } + + public static final Creator CREATOR = + new Creator() { + @Override + public ParcelableCallResponse createFromParcel(Parcel in) { + return new ParcelableCallResponse(in); + } + + @Override + public ParcelableCallResponse[] newArray(int size) { + return new ParcelableCallResponse[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeBoolean(mShouldDisallowCall); + dest.writeBoolean(mShouldRejectCall); + dest.writeBoolean(mShouldSilenceCall); + dest.writeBoolean(mShouldSkipCallLog); + dest.writeBoolean(mShouldSkipNotification); + dest.writeBoolean(mShouldScreenCallViaAudioProcessing); + } + } + /* * Information about how to respond to an incoming call. */ @@ -237,6 +344,38 @@ public abstract class CallScreeningService extends Service { return mShouldScreenCallViaAudioProcessing; } + /** @hide */ + public ParcelableCallResponse toParcelable() { + return new ParcelableCallResponse( + mShouldDisallowCall, + mShouldRejectCall, + mShouldSilenceCall, + mShouldSkipCallLog, + mShouldSkipNotification, + mShouldScreenCallViaAudioProcessing + ); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CallResponse that = (CallResponse) o; + return mShouldDisallowCall == that.mShouldDisallowCall && + mShouldRejectCall == that.mShouldRejectCall && + mShouldSilenceCall == that.mShouldSilenceCall && + mShouldSkipCallLog == that.mShouldSkipCallLog && + mShouldSkipNotification == that.mShouldSkipNotification && + mShouldScreenCallViaAudioProcessing == that.mShouldScreenCallViaAudioProcessing; + } + + @Override + public int hashCode() { + return Objects.hash(mShouldDisallowCall, mShouldRejectCall, mShouldSilenceCall, + mShouldSkipCallLog, mShouldSkipNotification, + mShouldScreenCallViaAudioProcessing); + } + public static class Builder { private boolean mShouldDisallowCall; private boolean mShouldRejectCall; @@ -423,21 +562,12 @@ public abstract class CallScreeningService extends Service { public final void respondToCall(@NonNull Call.Details callDetails, @NonNull CallResponse response) { try { - if (response.getDisallowCall()) { - mCallScreeningAdapter.disallowCall( - callDetails.getTelecomCallId(), - response.getRejectCall(), - !response.getSkipCallLog(), - !response.getSkipNotification(), - new ComponentName(getPackageName(), getClass().getName())); - } else if (response.getSilenceCall()) { - mCallScreeningAdapter.silenceCall(callDetails.getTelecomCallId()); - } else if (response.getShouldScreenCallViaAudioProcessing()) { - mCallScreeningAdapter.screenCallFurther(callDetails.getTelecomCallId()); - } else { - mCallScreeningAdapter.allowCall(callDetails.getTelecomCallId()); - } + mCallScreeningAdapter.onScreeningResponse( + callDetails.getTelecomCallId(), + new ComponentName(getPackageName(), getClass().getName()), + response.toParcelable()); } catch (RemoteException e) { + Log.e(this, e, "Got remote exception when returning response"); } } } diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java index 089a948b6e55f..942a54eb98bad 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -3390,11 +3390,20 @@ public abstract class Connection extends Conferenceable { * {@code true}, {@link #onDisconnect()} will be called soon after * this is called. * @param isInContacts Indicates whether the caller is in the user's contacts list. + * @param callScreeningResponse The response that was returned from the + * {@link CallScreeningService} that handled this call. If no + * response was received from a call screening service, + * this will be {@code null}. + * @param isResponseFromSystemDialer Whether {@code callScreeningResponse} was sent from the + * system dialer. If {@code callScreeningResponse} is + * {@code null}, this will be {@code false}. * @hide */ @SystemApi @RequiresPermission(Manifest.permission.READ_CONTACTS) - public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts) { } + public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts, + @Nullable CallScreeningService.CallResponse callScreeningResponse, + boolean isResponseFromSystemDialer) { } static String toLogSafePhoneNumber(String number) { // For unknown number, log empty string. diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java index 3b06fd3d4ea11..d9b108b70d512 100755 --- a/telecomm/java/android/telecom/ConnectionService.java +++ b/telecomm/java/android/telecom/ConnectionService.java @@ -759,6 +759,8 @@ public abstract class ConnectionService extends Service { @Override public void onCallFilteringCompleted(String callId, boolean isBlocked, boolean isInContacts, + CallScreeningService.ParcelableCallResponse callScreeningResponse, + boolean isResponseFromSystemDialer, Session.Info sessionInfo) { Log.startSession(sessionInfo, SESSION_CALL_FILTERING_COMPLETED); try { @@ -766,7 +768,9 @@ public abstract class ConnectionService extends Service { args.arg1 = callId; args.arg2 = isBlocked; args.arg3 = isInContacts; - args.arg4 = Log.createSubsession(); + args.arg4 = callScreeningResponse; + args.arg5 = isResponseFromSystemDialer; + args.arg6 = Log.createSubsession(); mHandler.obtainMessage(MSG_ON_CALL_FILTERING_COMPLETED, args).sendToTarget(); } finally { Log.endSession(); @@ -1437,12 +1441,16 @@ public abstract class ConnectionService extends Service { case MSG_ON_CALL_FILTERING_COMPLETED: { SomeArgs args = (SomeArgs) msg.obj; try { - Log.continueSession((Session) args.arg4, + Log.continueSession((Session) args.arg6, SESSION_HANDLER + SESSION_CALL_FILTERING_COMPLETED); String callId = (String) args.arg1; boolean isBlocked = (boolean) args.arg2; boolean isInContacts = (boolean) args.arg3; - onCallFilteringCompleted(callId, isBlocked, isInContacts); + CallScreeningService.ParcelableCallResponse callScreeningResponse = + (CallScreeningService.ParcelableCallResponse) args.arg4; + boolean isResponseFromSystemDialer = (boolean) args.arg5; + onCallFilteringCompleted(callId, isBlocked, isInContacts, + callScreeningResponse, isResponseFromSystemDialer); } finally { args.recycle(); Log.endSession(); @@ -2458,11 +2466,16 @@ public abstract class ConnectionService extends Service { } } - private void onCallFilteringCompleted(String callId, boolean isBlocked, boolean isInContacts) { - Log.i(this, "onCallFilteringCompleted(%b, %b)", isBlocked, isInContacts); + private void onCallFilteringCompleted(String callId, boolean isBlocked, boolean isInContacts, + CallScreeningService.ParcelableCallResponse callScreeningResponse, + boolean isResponseFromSystemDialer) { + Log.i(this, "onCallFilteringCompleted(%s, %b, %b, %s, %b)", callId, + isBlocked, isInContacts, callScreeningResponse, isResponseFromSystemDialer); Connection connection = findConnectionForAction(callId, "onCallFilteringCompleted"); if (connection != null) { - connection.onCallFilteringCompleted(isBlocked, isInContacts); + connection.onCallFilteringCompleted(isBlocked, isInContacts, + callScreeningResponse == null ? null : callScreeningResponse.toCallResponse(), + isResponseFromSystemDialer); } } diff --git a/telecomm/java/android/telecom/RemoteConnection.java b/telecomm/java/android/telecom/RemoteConnection.java index feb2ca53bbbe0..6c6097ac71e55 100644 --- a/telecomm/java/android/telecom/RemoteConnection.java +++ b/telecomm/java/android/telecom/RemoteConnection.java @@ -1204,15 +1204,25 @@ public final class RemoteConnection { * the results of a contacts lookup for the remote party. * @param isBlocked Whether call filtering indicates that the call should be blocked * @param isInContacts Whether the remote party is in the user's contacts + * @param callScreeningResponse The response that was returned from the + * {@link CallScreeningService} that handled this call. If no + * response was received from a call screening service, + * this will be {@code null}. + * @param isResponseFromSystemDialer Whether {@code callScreeningResponse} was sent from the + * system dialer. If {@code callScreeningResponse} is + * {@code null}, this will be {@code false}. * @hide */ @SystemApi @RequiresPermission(Manifest.permission.READ_CONTACTS) - public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts) { + public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts, + @Nullable CallScreeningService.CallResponse callScreeningResponse, + boolean isResponseFromSystemDialer) { Log.startSession("RC.oCFC", getActiveOwnerInfo()); try { if (mConnected) { mConnectionService.onCallFilteringCompleted(mConnectionId, isBlocked, isInContacts, + callScreeningResponse.toParcelable(), isResponseFromSystemDialer, null /*Session.Info*/); } } catch (RemoteException ignored) { diff --git a/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl b/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl index 83c8f62bb3dbd..0f2e178f1970f 100644 --- a/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl +++ b/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl @@ -17,6 +17,7 @@ package com.android.internal.telecom; import android.content.ComponentName; +import android.telecom.CallScreeningService; /** * Internal remote callback interface for call screening services. @@ -26,16 +27,6 @@ import android.content.ComponentName; * {@hide} */ oneway interface ICallScreeningAdapter { - void allowCall(String callId); - - void silenceCall(String callId); - - void screenCallFurther(String callId); - - void disallowCall( - String callId, - boolean shouldReject, - boolean shouldAddToCallLog, - boolean shouldShowNotification, - in ComponentName componentName); + void onScreeningResponse(String callId, in ComponentName componentName, + in CallScreeningService.ParcelableCallResponse response); } diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl index 301c2bb6cdb28..7599e189cc37b 100644 --- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl +++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl @@ -20,6 +20,7 @@ import android.net.Uri; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.telecom.CallAudioState; +import android.telecom.CallScreeningService; import android.telecom.ConnectionRequest; import android.telecom.Logging.Session; import android.telecom.PhoneAccountHandle; @@ -119,7 +120,8 @@ oneway interface IConnectionService { void sendCallEvent(String callId, String event, in Bundle extras, in Session.Info sessionInfo); void onCallFilteringCompleted(String callId, boolean isBlocked, boolean isInContacts, - in Session.Info sessionInfo); + in CallScreeningService.ParcelableCallResponse callScreeningResponse, + boolean isResponseFromSystemDialer, in Session.Info sessionInfo); void onExtrasChanged(String callId, in Bundle extras, in Session.Info sessionInfo); From d4c62b99d59dec30a6fb62e2af0c91074c126bcd Mon Sep 17 00:00:00 2001 From: Hall Liu Date: Fri, 5 Feb 2021 18:33:05 -0800 Subject: [PATCH 2/2] Add APIs to support call composer filtering Add APIs in callscreeningservice to allow the screening service to specify which call composer attachments should be shown to the user. Fixes: 179412110 Test: atest ConnectionServiceTest ExtendedInCallServiceTest Change-Id: Ib7d3213ecda8f612461e6e36375c16261fc5492a --- core/api/current.txt | 6 + .../android/telecom/CallScreeningService.java | 131 ++++++++++++++++-- 2 files changed, 124 insertions(+), 13 deletions(-) diff --git a/core/api/current.txt b/core/api/current.txt index a9458897dc4f3..f4dc9c0825d65 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -39072,16 +39072,22 @@ package android.telecom { } public static class CallScreeningService.CallResponse { + method public int getCallComposerAttachmentsToShow(); method public boolean getDisallowCall(); method public boolean getRejectCall(); method public boolean getSilenceCall(); method public boolean getSkipCallLog(); method public boolean getSkipNotification(); + field public static final int CALL_COMPOSER_ATTACHMENT_LOCATION = 2; // 0x2 + field public static final int CALL_COMPOSER_ATTACHMENT_PICTURE = 1; // 0x1 + field public static final int CALL_COMPOSER_ATTACHMENT_PRIORITY = 8; // 0x8 + field public static final int CALL_COMPOSER_ATTACHMENT_SUBJECT = 4; // 0x4 } public static class CallScreeningService.CallResponse.Builder { ctor public CallScreeningService.CallResponse.Builder(); method public android.telecom.CallScreeningService.CallResponse build(); + method @NonNull public android.telecom.CallScreeningService.CallResponse.Builder setCallComposerAttachmentsToShow(int); method public android.telecom.CallScreeningService.CallResponse.Builder setDisallowCall(boolean); method public android.telecom.CallScreeningService.CallResponse.Builder setRejectCall(boolean); method @NonNull public android.telecom.CallScreeningService.CallResponse.Builder setSilenceCall(boolean); diff --git a/telecomm/java/android/telecom/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java index b7f59e113372b..deeb4331bcaa8 100644 --- a/telecomm/java/android/telecom/CallScreeningService.java +++ b/telecomm/java/android/telecom/CallScreeningService.java @@ -17,6 +17,7 @@ package android.telecom; import android.Manifest; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; @@ -38,6 +39,8 @@ import com.android.internal.os.SomeArgs; import com.android.internal.telecom.ICallScreeningAdapter; import com.android.internal.telecom.ICallScreeningService; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** @@ -176,19 +179,23 @@ public abstract class CallScreeningService extends Service { private final boolean mShouldSkipNotification; private final boolean mShouldScreenCallViaAudioProcessing; + private final int mCallComposerAttachmentsToShow; + private ParcelableCallResponse( - boolean shouldDisallowCall, - boolean shouldRejectCall, - boolean shouldSilenceCall, - boolean shouldSkipCallLog, - boolean shouldSkipNotification, - boolean shouldScreenCallViaAudioProcessing) { + boolean shouldDisallowCall, + boolean shouldRejectCall, + boolean shouldSilenceCall, + boolean shouldSkipCallLog, + boolean shouldSkipNotification, + boolean shouldScreenCallViaAudioProcessing, + int callComposerAttachmentsToShow) { mShouldDisallowCall = shouldDisallowCall; mShouldRejectCall = shouldRejectCall; mShouldSilenceCall = shouldSilenceCall; mShouldSkipCallLog = shouldSkipCallLog; mShouldSkipNotification = shouldSkipNotification; mShouldScreenCallViaAudioProcessing = shouldScreenCallViaAudioProcessing; + mCallComposerAttachmentsToShow = callComposerAttachmentsToShow; } protected ParcelableCallResponse(Parcel in) { @@ -198,6 +205,7 @@ public abstract class CallScreeningService extends Service { mShouldSkipCallLog = in.readBoolean(); mShouldSkipNotification = in.readBoolean(); mShouldScreenCallViaAudioProcessing = in.readBoolean(); + mCallComposerAttachmentsToShow = in.readInt(); } public CallResponse toCallResponse() { @@ -208,6 +216,7 @@ public abstract class CallScreeningService extends Service { .setSkipCallLog(mShouldSkipCallLog) .setSkipNotification(mShouldSkipNotification) .setShouldScreenCallViaAudioProcessing(mShouldScreenCallViaAudioProcessing) + .setCallComposerAttachmentsToShow(mCallComposerAttachmentsToShow) .build(); } @@ -235,6 +244,10 @@ public abstract class CallScreeningService extends Service { return mShouldScreenCallViaAudioProcessing; } + public int getCallComposerAttachmentsToShow() { + return mCallComposerAttachmentsToShow; + } + public static final Creator CREATOR = new Creator() { @Override @@ -261,19 +274,64 @@ public abstract class CallScreeningService extends Service { dest.writeBoolean(mShouldSkipCallLog); dest.writeBoolean(mShouldSkipNotification); dest.writeBoolean(mShouldScreenCallViaAudioProcessing); + dest.writeInt(mCallComposerAttachmentsToShow); } } - /* - * Information about how to respond to an incoming call. + /** + * Information about how to respond to an incoming call. Call screening apps can construct an + * instance of this class using {@link CallResponse.Builder}. */ public static class CallResponse { + /** + * Bit flag indicating whether to show the picture attachment for call composer. + * + * Used with {@link Builder#setCallComposerAttachmentsToShow(int)}. + */ + public static final int CALL_COMPOSER_ATTACHMENT_PICTURE = 1; + + /** + * Bit flag indicating whether to show the location attachment for call composer. + * + * Used with {@link Builder#setCallComposerAttachmentsToShow(int)}. + */ + public static final int CALL_COMPOSER_ATTACHMENT_LOCATION = 1 << 1; + + /** + * Bit flag indicating whether to show the subject attachment for call composer. + * + * Used with {@link Builder#setCallComposerAttachmentsToShow(int)}. + */ + public static final int CALL_COMPOSER_ATTACHMENT_SUBJECT = 1 << 2; + + /** + * Bit flag indicating whether to show the priority attachment for call composer. + * + * Used with {@link Builder#setCallComposerAttachmentsToShow(int)}. + */ + public static final int CALL_COMPOSER_ATTACHMENT_PRIORITY = 1 << 3; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "CALL_COMPOSER_ATTACHMENT_", flag = true, + value = { + CALL_COMPOSER_ATTACHMENT_PICTURE, + CALL_COMPOSER_ATTACHMENT_LOCATION, + CALL_COMPOSER_ATTACHMENT_SUBJECT, + CALL_COMPOSER_ATTACHMENT_PRIORITY + } + ) + public @interface CallComposerAttachmentType {} + + private static final int NUM_CALL_COMPOSER_ATTACHMENT_TYPES = 4; + private final boolean mShouldDisallowCall; private final boolean mShouldRejectCall; private final boolean mShouldSilenceCall; private final boolean mShouldSkipCallLog; private final boolean mShouldSkipNotification; private final boolean mShouldScreenCallViaAudioProcessing; + private final int mCallComposerAttachmentsToShow; private CallResponse( boolean shouldDisallowCall, @@ -281,7 +339,8 @@ public abstract class CallScreeningService extends Service { boolean shouldSilenceCall, boolean shouldSkipCallLog, boolean shouldSkipNotification, - boolean shouldScreenCallViaAudioProcessing) { + boolean shouldScreenCallViaAudioProcessing, + int callComposerAttachmentsToShow) { if (!shouldDisallowCall && (shouldRejectCall || shouldSkipCallLog || shouldSkipNotification)) { throw new IllegalStateException("Invalid response state for allowed call."); @@ -297,6 +356,7 @@ public abstract class CallScreeningService extends Service { mShouldSkipNotification = shouldSkipNotification; mShouldSilenceCall = shouldSilenceCall; mShouldScreenCallViaAudioProcessing = shouldScreenCallViaAudioProcessing; + mCallComposerAttachmentsToShow = callComposerAttachmentsToShow; } /* @@ -344,6 +404,13 @@ public abstract class CallScreeningService extends Service { return mShouldScreenCallViaAudioProcessing; } + /** + * @return A bitmask of call composer attachments that should be shown to the user. + */ + public @CallComposerAttachmentType int getCallComposerAttachmentsToShow() { + return mCallComposerAttachmentsToShow; + } + /** @hide */ public ParcelableCallResponse toParcelable() { return new ParcelableCallResponse( @@ -352,7 +419,8 @@ public abstract class CallScreeningService extends Service { mShouldSilenceCall, mShouldSkipCallLog, mShouldSkipNotification, - mShouldScreenCallViaAudioProcessing + mShouldScreenCallViaAudioProcessing, + mCallComposerAttachmentsToShow ); } @@ -366,14 +434,17 @@ public abstract class CallScreeningService extends Service { mShouldSilenceCall == that.mShouldSilenceCall && mShouldSkipCallLog == that.mShouldSkipCallLog && mShouldSkipNotification == that.mShouldSkipNotification && - mShouldScreenCallViaAudioProcessing == that.mShouldScreenCallViaAudioProcessing; + mShouldScreenCallViaAudioProcessing + == that.mShouldScreenCallViaAudioProcessing && + mCallComposerAttachmentsToShow == that.mCallComposerAttachmentsToShow; } @Override public int hashCode() { return Objects.hash(mShouldDisallowCall, mShouldRejectCall, mShouldSilenceCall, mShouldSkipCallLog, mShouldSkipNotification, - mShouldScreenCallViaAudioProcessing); + mShouldScreenCallViaAudioProcessing, + mCallComposerAttachmentsToShow); } public static class Builder { @@ -383,6 +454,7 @@ public abstract class CallScreeningService extends Service { private boolean mShouldSkipCallLog; private boolean mShouldSkipNotification; private boolean mShouldScreenCallViaAudioProcessing; + private int mCallComposerAttachmentsToShow = -1; /** * Sets whether the incoming call should be blocked. @@ -468,6 +540,38 @@ public abstract class CallScreeningService extends Service { return this; } + /** + * Sets the call composer attachments that should be shown to the user. + * + * Attachments that are not shown will not be passed to the in-call UI responsible for + * displaying the call to the user. + * + * If this method is not called on a {@link Builder}, all attachments will be shown, + * except pictures, which will only be shown to users if the call is from a contact. + * + * Setting attachments to show will have no effect if the call screening service does + * not belong to the same package as the system dialer (as returned by + * {@link TelecomManager#getSystemDialerPackage()}). + * + * @param callComposerAttachmentsToShow A bitmask of call composer attachments to show. + */ + public @NonNull Builder setCallComposerAttachmentsToShow( + @CallComposerAttachmentType int callComposerAttachmentsToShow) { + // If the argument is less than zero (meaning unset), no-op since the conversion + // to/from the parcelable version may call with that value. + if (callComposerAttachmentsToShow < 0) { + return this; + } + + if ((callComposerAttachmentsToShow + & (1 << NUM_CALL_COMPOSER_ATTACHMENT_TYPES)) != 0) { + throw new IllegalArgumentException("Attachment types must match the ones" + + " defined in CallResponse"); + } + mCallComposerAttachmentsToShow = callComposerAttachmentsToShow; + return this; + } + public CallResponse build() { return new CallResponse( mShouldDisallowCall, @@ -475,7 +579,8 @@ public abstract class CallScreeningService extends Service { mShouldSilenceCall, mShouldSkipCallLog, mShouldSkipNotification, - mShouldScreenCallViaAudioProcessing); + mShouldScreenCallViaAudioProcessing, + mCallComposerAttachmentsToShow); } } }