| /* |
| * Copyright (C) 2018 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.ResolveInfo; |
| import android.os.Handler; |
| import android.os.IBinder; |
| import android.os.RemoteException; |
| import android.os.UserHandle; |
| import android.telecom.CallScreeningService; |
| import android.telecom.Log; |
| import android.telecom.Logging.Session; |
| import android.text.TextUtils; |
| |
| import com.android.internal.telecom.ICallScreeningAdapter; |
| import com.android.internal.telecom.ICallScreeningService; |
| |
| import java.util.List; |
| import java.util.concurrent.CompletableFuture; |
| |
| /** |
| * Helper class for performing operations with {@link CallScreeningService}s. |
| */ |
| public class CallScreeningServiceHelper { |
| private static final String TAG = CallScreeningServiceHelper.class.getSimpleName(); |
| |
| /** |
| * Implementation of {@link CallScreeningService} adapter AIDL; provides a means for responses |
| * from the call screening service to be handled. |
| */ |
| private class CallScreeningAdapter extends ICallScreeningAdapter.Stub { |
| private ServiceConnection mServiceConnection; |
| |
| public CallScreeningAdapter(ServiceConnection connection) { |
| mServiceConnection = connection; |
| } |
| |
| @Override |
| public void onScreeningResponse(String callId, ComponentName componentName, |
| CallScreeningService.ParcelableCallResponse callResponse) { |
| unbindCallScreeningService(); |
| } |
| |
| private void unbindCallScreeningService() { |
| mContext.unbindService(mServiceConnection); |
| } |
| } |
| |
| private final ParcelableCallUtils.Converter mParcelableCallUtilsConverter; |
| private final TelecomSystem.SyncRoot mTelecomLock; |
| private final Call mCall; |
| private final UserHandle mUserHandle; |
| private final Context mContext; |
| private final AppLabelProxy mAppLabelProxy; |
| private final Session mLoggingSession; |
| private CompletableFuture mFuture; |
| private String mPackageName; |
| |
| public CallScreeningServiceHelper(Context context, TelecomSystem.SyncRoot telecomLock, |
| String packageName, ParcelableCallUtils.Converter converter, |
| UserHandle userHandle, Call call, AppLabelProxy appLabelProxy) { |
| mContext = context; |
| mTelecomLock = telecomLock; |
| mParcelableCallUtilsConverter = converter; |
| mCall = call; |
| mUserHandle = userHandle; |
| mPackageName = packageName; |
| mAppLabelProxy = appLabelProxy; |
| mLoggingSession = Log.createSubsession(); |
| } |
| |
| /** |
| * Builds a {@link CompletableFuture} which performs a bind to a {@link CallScreeningService} |
| * @return |
| */ |
| public CompletableFuture process() { |
| Log.d(this, "process"); |
| return bindAndGetCallIdentification(); |
| } |
| |
| public CompletableFuture bindAndGetCallIdentification() { |
| Log.d(this, "bindAndGetCallIdentification"); |
| if (mPackageName == null) { |
| return CompletableFuture.completedFuture(null); |
| } |
| |
| mFuture = new CompletableFuture(); |
| |
| ServiceConnection serviceConnection = new ServiceConnection() { |
| @Override |
| public void onServiceConnected(ComponentName name, IBinder service) { |
| ICallScreeningService screeningService = |
| ICallScreeningService.Stub.asInterface(service); |
| Log.continueSession(mLoggingSession, "CSSH.oSC"); |
| try { |
| try { |
| // Note: for outgoing calls, never include the restricted extras. |
| screeningService.screenCall(new CallScreeningAdapter(this), |
| mParcelableCallUtilsConverter.toParcelableCallForScreening(mCall, |
| false /* areRestrictedExtrasIncluded */)); |
| } catch (RemoteException e) { |
| Log.w(CallScreeningServiceHelper.this, |
| "Cancelling call id due to remote exception"); |
| mFuture.complete(null); |
| } |
| } finally { |
| Log.endSession(); |
| } |
| } |
| |
| @Override |
| public void onServiceDisconnected(ComponentName name) { |
| // No locking needed -- CompletableFuture only lets one thread call complete. |
| Log.continueSession(mLoggingSession, "CSSH.oSD"); |
| try { |
| if (!mFuture.isDone()) { |
| Log.w(CallScreeningServiceHelper.this, |
| "Cancelling outgoing call screen due to service disconnect."); |
| } |
| mFuture.complete(null); |
| } finally { |
| Log.endSession(); |
| } |
| } |
| }; |
| |
| if (!bindCallScreeningService(mContext, mUserHandle, mPackageName, serviceConnection)) { |
| Log.i(this, "bindAndGetCallIdentification - bind failed"); |
| mFuture.complete(null); |
| } |
| Log.addEvent(mCall, LogUtils.Events.BIND_SCREENING, mPackageName); |
| |
| // Set up a timeout so that we're not waiting forever for the caller ID information. |
| Handler handler = new Handler(); |
| handler.postDelayed(() -> { |
| // No locking needed -- CompletableFuture only lets one thread call complete. |
| Log.continueSession(mLoggingSession, "CSSH.timeout"); |
| try { |
| if (!mFuture.isDone()) { |
| Log.w(TAG, "Cancelling call id process due to timeout"); |
| } |
| mFuture.complete(null); |
| } finally { |
| Log.endSession(); |
| } |
| }, |
| Timeouts.getCallScreeningTimeoutMillis(mContext.getContentResolver())); |
| return mFuture; |
| } |
| |
| /** |
| * Binds to a {@link CallScreeningService}. |
| * @param context The current context. |
| * @param userHandle User to bind as. |
| * @param packageName Package name of the {@link CallScreeningService}. |
| * @param serviceConnection The {@link ServiceConnection} to be notified of binding. |
| * @return {@code true} if binding succeeds, {@code false} otherwise. |
| */ |
| public static boolean bindCallScreeningService(Context context, UserHandle userHandle, |
| String packageName, ServiceConnection serviceConnection) { |
| if (TextUtils.isEmpty(packageName)) { |
| Log.i(TAG, "PackageName is empty. Not performing call screening."); |
| return false; |
| } |
| |
| Intent intent = new Intent(CallScreeningService.SERVICE_INTERFACE) |
| .setPackage(packageName); |
| List<ResolveInfo> entries = context.getPackageManager().queryIntentServicesAsUser( |
| intent, 0, userHandle.getIdentifier()); |
| if (entries.isEmpty()) { |
| Log.i(TAG, packageName + " has no call screening service defined."); |
| return false; |
| } |
| |
| ResolveInfo entry = entries.get(0); |
| if (entry.serviceInfo == null) { |
| Log.w(TAG, packageName + " 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(TAG, "CallScreeningService must require BIND_SCREENING_SERVICE permission: " + |
| entry.serviceInfo.packageName); |
| return false; |
| } |
| |
| ComponentName componentName = |
| new ComponentName(entry.serviceInfo.packageName, entry.serviceInfo.name); |
| intent.setComponent(componentName); |
| if (context.bindServiceAsUser( |
| intent, |
| serviceConnection, |
| Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, |
| UserHandle.CURRENT)) { |
| Log.d(TAG, "bindService, found service, waiting for it to connect"); |
| return true; |
| } |
| |
| return false; |
| } |
| } |