/* * 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.Manifest; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SystemApi; import android.app.Service; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ServiceInfo; import android.net.Uri; 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.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; 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 * incoming calls before they are shown to a user. A {@link CallScreeningService} can also see * outgoing calls for the purpose of providing caller ID services for those calls. *
* Below is an example manifest registration for a {@code CallScreeningService}. *
* {@code *** * } ** **
* A CallScreeningService performs two functions: *
*
* The code snippet below illustrates how your app can request that it fills the call screening * role. *
* {@code * private static final int REQUEST_ID = 1; * * public void requestRole() { * RoleManager roleManager = (RoleManager) getSystemService(ROLE_SERVICE); * Intent intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_CALL_SCREENING); * startActivityForResult(intent, REQUEST_ID); * } * * @Override * public void onActivityResult(int requestCode, int resultCode, Intent data) { * if (requestCode == REQUEST_ID) { * if (resultCode == android.app.Activity.RESULT_OK) { * // Your app is now the call screening app * } else { * // Your app is not the call screening app * } * } * } * } ** *
* For incoming calls, the {@link CallScreeningService} must call * {@link #respondToCall(Call.Details, CallResponse)} within 5 seconds of being bound to indicate to * the platform whether the call should be blocked or not. Your app must do this even if it is * primarily performing caller ID operations and not screening calls. It is important to perform * screening operations in a timely matter as the user's device will not begin ringing until the * response is received (or the timeout is hit). A {@link CallScreeningService} may choose to * perform local database lookups to help determine if a call should be screened or not; care should * be taken to ensure the timeout is not repeatedly hit, causing delays in the incoming call flow. *
* If your app provides a caller ID experience, it should launch an activity to show the caller ID
* information from {@link #onScreenCall(Call.Details)}.
*/
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;
Call.Details callDetails = Call.Details
.createFromParcelableCall((ParcelableCall) args.arg2);
onScreenCall(callDetails);
if (callDetails.getCallDirection() == Call.Details.DIRECTION_OUTGOING) {
mCallScreeningAdapter.onScreeningResponse(
callDetails.getTelecomCallId(),
new ComponentName(getPackageName(), getClass().getName()),
null);
}
} catch (RemoteException e) {
Log.w(this, "Exception when screening call: " + e);
} 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;
/**
* 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 final int mCallComposerAttachmentsToShow;
private ParcelableCallResponse(
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) {
mShouldDisallowCall = in.readBoolean();
mShouldRejectCall = in.readBoolean();
mShouldSilenceCall = in.readBoolean();
mShouldSkipCallLog = in.readBoolean();
mShouldSkipNotification = in.readBoolean();
mShouldScreenCallViaAudioProcessing = in.readBoolean();
mCallComposerAttachmentsToShow = in.readInt();
}
public CallResponse toCallResponse() {
return new CallResponse.Builder()
.setDisallowCall(mShouldDisallowCall)
.setRejectCall(mShouldRejectCall)
.setSilenceCall(mShouldSilenceCall)
.setSkipCallLog(mShouldSkipCallLog)
.setSkipNotification(mShouldSkipNotification)
.setShouldScreenCallViaAudioProcessing(mShouldScreenCallViaAudioProcessing)
.setCallComposerAttachmentsToShow(mCallComposerAttachmentsToShow)
.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 int getCallComposerAttachmentsToShow() {
return mCallComposerAttachmentsToShow;
}
public static final Creator
* Note: Calls will still be logged with type
* {@link android.provider.CallLog.Calls#BLOCKED_TYPE}, regardless of how this property
* is set.
*/
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;
}
/**
* Sets whether to request background audio processing so that the in-call service can
* screen the call further. If set to {@code true}, {@link #setDisallowCall} should be
* called with {@code false}, and all other parameters in this builder will be ignored.
*
* This request will only be honored if the {@link CallScreeningService} shares the same
* uid as the system dialer app. Otherwise, the call will go through as usual.
*
* Apps built with SDK version {@link android.os.Build.VERSION_CODES#R} or later which
* are using the microphone as part of audio processing should specify the
* foreground service type using the attribute
* {@link android.R.attr#foregroundServiceType} in the {@link CallScreeningService}
* service element of the app's manifest file.
* The {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_MICROPHONE} attribute should be
* specified.
* @see
*
* the Android Developer Site for more information.
*
* @param shouldScreenCallViaAudioProcessing Whether to request further call screening.
* @hide
*/
@SystemApi
@RequiresPermission(Manifest.permission.CAPTURE_AUDIO_OUTPUT)
public @NonNull Builder setShouldScreenCallViaAudioProcessing(
boolean shouldScreenCallViaAudioProcessing) {
mShouldScreenCallViaAudioProcessing = shouldScreenCallViaAudioProcessing;
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,
mShouldRejectCall,
mShouldSilenceCall,
mShouldSkipCallLog,
mShouldSkipNotification,
mShouldScreenCallViaAudioProcessing,
mCallComposerAttachmentsToShow);
}
}
}
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 or outgoing call is added.
*
* A {@link CallScreeningService} must indicate whether an incoming call is allowed or not by
* calling
* {@link CallScreeningService#respondToCall(Call.Details, CallScreeningService.CallResponse)}.
* Your app can tell if a call is an incoming call by checking to see if
* {@link Call.Details#getCallDirection()} is {@link Call.Details#DIRECTION_INCOMING}.
*
* Note: A {@link CallScreeningService} must respond to a call within 5 seconds. After
* this time, the framework will unbind from the {@link CallScreeningService} and ignore its
* response.
*
* Note: The {@link Call.Details} instance provided to a call screening service will
* only have the following properties set. The rest of the {@link Call.Details} properties will
* be set to their default value or {@code null}.
*
* Only calls where the {@link Call.Details#getHandle() handle} {@link Uri#getScheme() scheme}
* is {@link PhoneAccount#SCHEME_TEL} are passed for call
* screening. Further, only calls which are not in the user's contacts are passed for
* screening, unless the {@link CallScreeningService} has been granted
* {@link Manifest.permission#READ_CONTACTS} permission by the user. For outgoing calls, no
* post-dial digits are passed.
*
* Calls with a {@link Call.Details#getHandlePresentation()} of
* {@link TelecomManager#PRESENTATION_RESTRICTED}, {@link TelecomManager#PRESENTATION_UNKNOWN}
* or {@link TelecomManager#PRESENTATION_PAYPHONE} presentation are not provided to the
* {@link CallScreeningService}.
*
* @param callDetails Information about a new call, see {@link Call.Details}.
*/
public abstract void onScreenCall(@NonNull Call.Details callDetails);
/**
* Responds to the given incoming call, either allowing it, silencing it or disallowing it.
*
* The {@link CallScreeningService} calls this method to inform the system whether the call
* should be silently blocked or not. In the event that it should not be blocked, it may
* also be requested to ring silently.
*
* Calls to this method are ignored unless the {@link Call.Details#getCallDirection()} is
* {@link Call.Details#DIRECTION_INCOMING}.
*
* For incoming calls, a {@link CallScreeningService} MUST call this method within 5 seconds of
* {@link #onScreenCall(Call.Details)} being invoked by the platform.
*
* Calls which are blocked/rejected will be logged to the system call log with a call type of
* {@link android.provider.CallLog.Calls#BLOCKED_TYPE} and
* {@link android.provider.CallLog.Calls#BLOCK_REASON_CALL_SCREENING_SERVICE} block reason.
*
* @param callDetails The call to allow.
*
* Must be the same {@link Call.Details call} which was provided to the
* {@link CallScreeningService} via {@link #onScreenCall(Call.Details)}.
* @param response The {@link CallScreeningService.CallResponse} which contains information
* about how to respond to a call.
*/
public final void respondToCall(@NonNull Call.Details callDetails,
@NonNull CallResponse response) {
try {
mCallScreeningAdapter.onScreeningResponse(
callDetails.getTelecomCallId(),
new ComponentName(getPackageName(), getClass().getName()),
response.toParcelable());
} catch (RemoteException e) {
Log.e(this, e, "Got remote exception when returning response");
}
}
}
*
*