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 onScreeningResponse(String callId, ComponentName componentName, CallScreeningService.ParcelableCallResponse callResponse)58 public void onScreeningResponse(String callId, ComponentName componentName, 59 CallScreeningService.ParcelableCallResponse callResponse) { 60 unbindCallScreeningService(); 61 } 62 unbindCallScreeningService()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 CallScreeningServiceHelper(Context context, TelecomSystem.SyncRoot telecomLock, String packageName, ParcelableCallUtils.Converter converter, UserHandle userHandle, Call call, AppLabelProxy appLabelProxy)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 */ process()95 public CompletableFuture process() { 96 Log.d(this, "process"); 97 return bindAndGetCallIdentification(); 98 } 99 bindAndGetCallIdentification()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 mContext.unbindService(this); 141 } finally { 142 Log.endSession(); 143 } 144 } 145 146 @Override 147 public void onNullBinding(ComponentName name) { 148 // No locking needed -- CompletableFuture only lets one thread call complete. 149 Log.continueSession(mLoggingSession, "CSSH.oNB"); 150 try { 151 if (!mFuture.isDone()) { 152 Log.w(CallScreeningServiceHelper.this, 153 "Cancelling outgoing call screen due to null binding."); 154 } 155 mFuture.complete(null); 156 mContext.unbindService(this); 157 } finally { 158 Log.endSession(); 159 } 160 } 161 }; 162 163 if (!bindCallScreeningService(mContext, mUserHandle, mPackageName, serviceConnection)) { 164 Log.i(this, "bindAndGetCallIdentification - bind failed"); 165 mFuture.complete(null); 166 } 167 Log.addEvent(mCall, LogUtils.Events.BIND_SCREENING, mPackageName); 168 169 // Set up a timeout so that we're not waiting forever for the caller ID information. 170 Handler handler = new Handler(); 171 handler.postDelayed(() -> { 172 // No locking needed -- CompletableFuture only lets one thread call complete. 173 Log.continueSession(mLoggingSession, "CSSH.timeout"); 174 try { 175 if (!mFuture.isDone()) { 176 Log.w(TAG, "Cancelling call id process due to timeout"); 177 } 178 mFuture.complete(null); 179 mContext.unbindService(serviceConnection); 180 } catch (IllegalArgumentException e) { 181 Log.i(this, "Exception when unbinding service %s : %s", serviceConnection, 182 e.getMessage()); 183 } finally { 184 Log.endSession(); 185 } 186 }, 187 Timeouts.getCallScreeningTimeoutMillis(mContext.getContentResolver())); 188 return mFuture; 189 } 190 191 /** 192 * Binds to a {@link CallScreeningService}. 193 * @param context The current context. 194 * @param userHandle User to bind as. 195 * @param packageName Package name of the {@link CallScreeningService}. 196 * @param serviceConnection The {@link ServiceConnection} to be notified of binding. 197 * @return {@code true} if binding succeeds, {@code false} otherwise. 198 */ bindCallScreeningService(Context context, UserHandle userHandle, String packageName, ServiceConnection serviceConnection)199 public static boolean bindCallScreeningService(Context context, UserHandle userHandle, 200 String packageName, ServiceConnection serviceConnection) { 201 if (TextUtils.isEmpty(packageName)) { 202 Log.i(TAG, "PackageName is empty. Not performing call screening."); 203 return false; 204 } 205 206 Intent intent = new Intent(CallScreeningService.SERVICE_INTERFACE) 207 .setPackage(packageName); 208 List<ResolveInfo> entries = context.getPackageManager().queryIntentServicesAsUser( 209 intent, 0, userHandle.getIdentifier()); 210 if (entries.isEmpty()) { 211 Log.i(TAG, packageName + " has no call screening service defined."); 212 return false; 213 } 214 215 ResolveInfo entry = entries.get(0); 216 if (entry.serviceInfo == null) { 217 Log.w(TAG, packageName + " call screening service has invalid service info"); 218 return false; 219 } 220 221 if (entry.serviceInfo.permission == null || !entry.serviceInfo.permission.equals( 222 Manifest.permission.BIND_SCREENING_SERVICE)) { 223 Log.w(TAG, "CallScreeningService must require BIND_SCREENING_SERVICE permission: " + 224 entry.serviceInfo.packageName); 225 return false; 226 } 227 228 ComponentName componentName = 229 new ComponentName(entry.serviceInfo.packageName, entry.serviceInfo.name); 230 intent.setComponent(componentName); 231 if (context.bindServiceAsUser( 232 intent, 233 serviceConnection, 234 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE 235 | Context.BIND_SCHEDULE_LIKE_TOP_APP, 236 userHandle)) { 237 Log.d(TAG,"bindServiceAsUser, found service," 238 + "waiting for it to connect to user: %s", userHandle); 239 return true; 240 } 241 242 return false; 243 } 244 } 245