1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.server.telecom; 18 19 import android.Manifest; 20 import android.content.ComponentName; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.ServiceConnection; 24 import android.content.pm.ResolveInfo; 25 import android.os.Handler; 26 import android.os.IBinder; 27 import android.os.RemoteException; 28 import android.os.UserHandle; 29 import android.telecom.CallScreeningService; 30 import android.telecom.Log; 31 import android.telecom.Logging.Session; 32 import android.text.TextUtils; 33 34 import com.android.internal.telecom.ICallScreeningAdapter; 35 import com.android.internal.telecom.ICallScreeningService; 36 37 import java.util.List; 38 import java.util.concurrent.CompletableFuture; 39 40 /** 41 * Helper class for performing operations with {@link CallScreeningService}s. 42 */ 43 public class CallScreeningServiceHelper { 44 private static final String TAG = CallScreeningServiceHelper.class.getSimpleName(); 45 46 /** 47 * Implementation of {@link CallScreeningService} adapter AIDL; provides a means for responses 48 * from the call screening service to be handled. 49 */ 50 private class CallScreeningAdapter extends ICallScreeningAdapter.Stub { 51 private ServiceConnection mServiceConnection; 52 53 public CallScreeningAdapter(ServiceConnection connection) { 54 mServiceConnection = connection; 55 } 56 57 @Override 58 public void onScreeningResponse(String callId, ComponentName componentName, 59 CallScreeningService.ParcelableCallResponse callResponse) { 60 unbindCallScreeningService(); 61 } 62 63 private void unbindCallScreeningService() { 64 mContext.unbindService(mServiceConnection); 65 } 66 } 67 68 private final ParcelableCallUtils.Converter mParcelableCallUtilsConverter; 69 private final TelecomSystem.SyncRoot mTelecomLock; 70 private final Call mCall; 71 private final UserHandle mUserHandle; 72 private final Context mContext; 73 private final AppLabelProxy mAppLabelProxy; 74 private final Session mLoggingSession; 75 private CompletableFuture mFuture; 76 private String mPackageName; 77 78 public CallScreeningServiceHelper(Context context, TelecomSystem.SyncRoot telecomLock, 79 String packageName, ParcelableCallUtils.Converter converter, 80 UserHandle userHandle, Call call, AppLabelProxy appLabelProxy) { 81 mContext = context; 82 mTelecomLock = telecomLock; 83 mParcelableCallUtilsConverter = converter; 84 mCall = call; 85 mUserHandle = userHandle; 86 mPackageName = packageName; 87 mAppLabelProxy = appLabelProxy; 88 mLoggingSession = Log.createSubsession(); 89 } 90 91 /** 92 * Builds a {@link CompletableFuture} which performs a bind to a {@link CallScreeningService} 93 * @return 94 */ 95 public CompletableFuture process() { 96 Log.d(this, "process"); 97 return bindAndGetCallIdentification(); 98 } 99 100 public CompletableFuture bindAndGetCallIdentification() { 101 Log.d(this, "bindAndGetCallIdentification"); 102 if (mPackageName == null) { 103 return CompletableFuture.completedFuture(null); 104 } 105 106 mFuture = new CompletableFuture(); 107 108 ServiceConnection serviceConnection = new ServiceConnection() { 109 @Override 110 public void onServiceConnected(ComponentName name, IBinder service) { 111 ICallScreeningService screeningService = 112 ICallScreeningService.Stub.asInterface(service); 113 Log.continueSession(mLoggingSession, "CSSH.oSC"); 114 try { 115 try { 116 // Note: for outgoing calls, never include the restricted extras. 117 screeningService.screenCall(new CallScreeningAdapter(this), 118 mParcelableCallUtilsConverter.toParcelableCallForScreening(mCall, 119 false /* areRestrictedExtrasIncluded */)); 120 } catch (RemoteException e) { 121 Log.w(CallScreeningServiceHelper.this, 122 "Cancelling call id due to remote exception"); 123 mFuture.complete(null); 124 } 125 } finally { 126 Log.endSession(); 127 } 128 } 129 130 @Override 131 public void onServiceDisconnected(ComponentName name) { 132 // No locking needed -- CompletableFuture only lets one thread call complete. 133 Log.continueSession(mLoggingSession, "CSSH.oSD"); 134 try { 135 if (!mFuture.isDone()) { 136 Log.w(CallScreeningServiceHelper.this, 137 "Cancelling outgoing call screen due to service disconnect."); 138 } 139 mFuture.complete(null); 140 } finally { 141 Log.endSession(); 142 } 143 } 144 }; 145 146 if (!bindCallScreeningService(mContext, mUserHandle, mPackageName, serviceConnection)) { 147 Log.i(this, "bindAndGetCallIdentification - bind failed"); 148 Log.addEvent(mCall, LogUtils.Events.BIND_SCREENING, mPackageName); 149 mFuture.complete(null); 150 } 151 152 // Set up a timeout so that we're not waiting forever for the caller ID information. 153 Handler handler = new Handler(); 154 handler.postDelayed(() -> { 155 // No locking needed -- CompletableFuture only lets one thread call complete. 156 Log.continueSession(mLoggingSession, "CSSH.timeout"); 157 try { 158 if (!mFuture.isDone()) { 159 Log.w(TAG, "Cancelling call id process due to timeout"); 160 } 161 mFuture.complete(null); 162 } finally { 163 Log.endSession(); 164 } 165 }, 166 Timeouts.getCallScreeningTimeoutMillis(mContext.getContentResolver())); 167 return mFuture; 168 } 169 170 /** 171 * Binds to a {@link CallScreeningService}. 172 * @param context The current context. 173 * @param userHandle User to bind as. 174 * @param packageName Package name of the {@link CallScreeningService}. 175 * @param serviceConnection The {@link ServiceConnection} to be notified of binding. 176 * @return {@code true} if binding succeeds, {@code false} otherwise. 177 */ 178 public static boolean bindCallScreeningService(Context context, UserHandle userHandle, 179 String packageName, ServiceConnection serviceConnection) { 180 if (TextUtils.isEmpty(packageName)) { 181 Log.i(TAG, "PackageName is empty. Not performing call screening."); 182 return false; 183 } 184 185 Intent intent = new Intent(CallScreeningService.SERVICE_INTERFACE) 186 .setPackage(packageName); 187 List<ResolveInfo> entries = context.getPackageManager().queryIntentServicesAsUser( 188 intent, 0, userHandle.getIdentifier()); 189 if (entries.isEmpty()) { 190 Log.i(TAG, packageName + " has no call screening service defined."); 191 return false; 192 } 193 194 ResolveInfo entry = entries.get(0); 195 if (entry.serviceInfo == null) { 196 Log.w(TAG, packageName + " call screening service has invalid service info"); 197 return false; 198 } 199 200 if (entry.serviceInfo.permission == null || !entry.serviceInfo.permission.equals( 201 Manifest.permission.BIND_SCREENING_SERVICE)) { 202 Log.w(TAG, "CallScreeningService must require BIND_SCREENING_SERVICE permission: " + 203 entry.serviceInfo.packageName); 204 return false; 205 } 206 207 ComponentName componentName = 208 new ComponentName(entry.serviceInfo.packageName, entry.serviceInfo.name); 209 intent.setComponent(componentName); 210 if (context.bindServiceAsUser( 211 intent, 212 serviceConnection, 213 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, 214 UserHandle.CURRENT)) { 215 Log.d(TAG, "bindService, found service, waiting for it to connect"); 216 return true; 217 } 218 219 return false; 220 } 221 } 222