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 CallScreeningAdapter(ServiceConnection connection)53 public CallScreeningAdapter(ServiceConnection connection) { 54 mServiceConnection = connection; 55 } 56 57 @Override allowCall(String s)58 public void allowCall(String s) throws RemoteException { 59 unbindCallScreeningService(); 60 } 61 62 @Override silenceCall(String s)63 public void silenceCall(String s) throws RemoteException { 64 unbindCallScreeningService(); 65 } 66 67 @Override screenCallFurther(String callId)68 public void screenCallFurther(String callId) throws RemoteException { 69 unbindCallScreeningService(); 70 } 71 72 @Override disallowCall(String s, boolean b, boolean b1, boolean b2, ComponentName componentName)73 public void disallowCall(String s, boolean b, boolean b1, boolean b2, 74 ComponentName componentName) throws RemoteException { 75 unbindCallScreeningService(); 76 } 77 unbindCallScreeningService()78 private void unbindCallScreeningService() { 79 mContext.unbindService(mServiceConnection); 80 } 81 } 82 83 private final ParcelableCallUtils.Converter mParcelableCallUtilsConverter; 84 private final TelecomSystem.SyncRoot mTelecomLock; 85 private final Call mCall; 86 private final UserHandle mUserHandle; 87 private final Context mContext; 88 private final AppLabelProxy mAppLabelProxy; 89 private final Session mLoggingSession; 90 private CompletableFuture mFuture; 91 private String mPackageName; 92 CallScreeningServiceHelper(Context context, TelecomSystem.SyncRoot telecomLock, String packageName, ParcelableCallUtils.Converter converter, UserHandle userHandle, Call call, AppLabelProxy appLabelProxy)93 public CallScreeningServiceHelper(Context context, TelecomSystem.SyncRoot telecomLock, 94 String packageName, ParcelableCallUtils.Converter converter, 95 UserHandle userHandle, Call call, AppLabelProxy appLabelProxy) { 96 mContext = context; 97 mTelecomLock = telecomLock; 98 mParcelableCallUtilsConverter = converter; 99 mCall = call; 100 mUserHandle = userHandle; 101 mPackageName = packageName; 102 mAppLabelProxy = appLabelProxy; 103 mLoggingSession = Log.createSubsession(); 104 } 105 106 /** 107 * Builds a {@link CompletableFuture} which performs a bind to a {@link CallScreeningService} 108 * @return 109 */ process()110 public CompletableFuture process() { 111 Log.d(this, "process"); 112 return bindAndGetCallIdentification(); 113 } 114 bindAndGetCallIdentification()115 public CompletableFuture bindAndGetCallIdentification() { 116 Log.d(this, "bindAndGetCallIdentification"); 117 if (mPackageName == null) { 118 return CompletableFuture.completedFuture(null); 119 } 120 121 mFuture = new CompletableFuture(); 122 123 ServiceConnection serviceConnection = new ServiceConnection() { 124 @Override 125 public void onServiceConnected(ComponentName name, IBinder service) { 126 ICallScreeningService screeningService = 127 ICallScreeningService.Stub.asInterface(service); 128 Log.continueSession(mLoggingSession, "CSSH.oSC"); 129 try { 130 try { 131 // Note: for outgoing calls, never include the restricted extras. 132 screeningService.screenCall(new CallScreeningAdapter(this), 133 mParcelableCallUtilsConverter.toParcelableCallForScreening(mCall, 134 false /* areRestrictedExtrasIncluded */)); 135 } catch (RemoteException e) { 136 Log.w(CallScreeningServiceHelper.this, 137 "Cancelling call id due to remote exception"); 138 mFuture.complete(null); 139 } 140 } finally { 141 Log.endSession(); 142 } 143 } 144 145 @Override 146 public void onServiceDisconnected(ComponentName name) { 147 // No locking needed -- CompletableFuture only lets one thread call complete. 148 Log.continueSession(mLoggingSession, "CSSH.oSD"); 149 try { 150 if (!mFuture.isDone()) { 151 Log.w(CallScreeningServiceHelper.this, 152 "Cancelling outgoing call screen due to service disconnect."); 153 } 154 mFuture.complete(null); 155 } finally { 156 Log.endSession(); 157 } 158 } 159 }; 160 161 if (!bindCallScreeningService(mContext, mUserHandle, mPackageName, serviceConnection)) { 162 Log.i(this, "bindAndGetCallIdentification - bind failed"); 163 Log.addEvent(mCall, LogUtils.Events.BIND_SCREENING, mPackageName); 164 mFuture.complete(null); 165 } 166 167 // Set up a timeout so that we're not waiting forever for the caller ID information. 168 Handler handler = new Handler(); 169 handler.postDelayed(() -> { 170 // No locking needed -- CompletableFuture only lets one thread call complete. 171 Log.continueSession(mLoggingSession, "CSSH.timeout"); 172 try { 173 if (!mFuture.isDone()) { 174 Log.w(TAG, "Cancelling call id process due to timeout"); 175 } 176 mFuture.complete(null); 177 } finally { 178 Log.endSession(); 179 } 180 }, 181 Timeouts.getCallScreeningTimeoutMillis(mContext.getContentResolver())); 182 return mFuture; 183 } 184 185 /** 186 * Binds to a {@link CallScreeningService}. 187 * @param context The current context. 188 * @param userHandle User to bind as. 189 * @param packageName Package name of the {@link CallScreeningService}. 190 * @param serviceConnection The {@link ServiceConnection} to be notified of binding. 191 * @return {@code true} if binding succeeds, {@code false} otherwise. 192 */ bindCallScreeningService(Context context, UserHandle userHandle, String packageName, ServiceConnection serviceConnection)193 public static boolean bindCallScreeningService(Context context, UserHandle userHandle, 194 String packageName, ServiceConnection serviceConnection) { 195 if (TextUtils.isEmpty(packageName)) { 196 Log.i(TAG, "PackageName is empty. Not performing call screening."); 197 return false; 198 } 199 200 Intent intent = new Intent(CallScreeningService.SERVICE_INTERFACE) 201 .setPackage(packageName); 202 List<ResolveInfo> entries = context.getPackageManager().queryIntentServicesAsUser( 203 intent, 0, userHandle.getIdentifier()); 204 if (entries.isEmpty()) { 205 Log.i(TAG, packageName + " has no call screening service defined."); 206 return false; 207 } 208 209 ResolveInfo entry = entries.get(0); 210 if (entry.serviceInfo == null) { 211 Log.w(TAG, packageName + " call screening service has invalid service info"); 212 return false; 213 } 214 215 if (entry.serviceInfo.permission == null || !entry.serviceInfo.permission.equals( 216 Manifest.permission.BIND_SCREENING_SERVICE)) { 217 Log.w(TAG, "CallScreeningService must require BIND_SCREENING_SERVICE permission: " + 218 entry.serviceInfo.packageName); 219 return false; 220 } 221 222 ComponentName componentName = 223 new ComponentName(entry.serviceInfo.packageName, entry.serviceInfo.name); 224 intent.setComponent(componentName); 225 if (context.bindServiceAsUser( 226 intent, 227 serviceConnection, 228 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, 229 UserHandle.CURRENT)) { 230 Log.d(TAG, "bindService, found service, waiting for it to connect"); 231 return true; 232 } 233 234 return false; 235 } 236 } 237