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 } finally { 180 Log.endSession(); 181 } 182 }, 183 Timeouts.getCallScreeningTimeoutMillis(mContext.getContentResolver())); 184 return mFuture; 185 } 186 187 /** 188 * Binds to a {@link CallScreeningService}. 189 * @param context The current context. 190 * @param userHandle User to bind as. 191 * @param packageName Package name of the {@link CallScreeningService}. 192 * @param serviceConnection The {@link ServiceConnection} to be notified of binding. 193 * @return {@code true} if binding succeeds, {@code false} otherwise. 194 */ bindCallScreeningService(Context context, UserHandle userHandle, String packageName, ServiceConnection serviceConnection)195 public static boolean bindCallScreeningService(Context context, UserHandle userHandle, 196 String packageName, ServiceConnection serviceConnection) { 197 if (TextUtils.isEmpty(packageName)) { 198 Log.i(TAG, "PackageName is empty. Not performing call screening."); 199 return false; 200 } 201 202 Intent intent = new Intent(CallScreeningService.SERVICE_INTERFACE) 203 .setPackage(packageName); 204 List<ResolveInfo> entries = context.getPackageManager().queryIntentServicesAsUser( 205 intent, 0, userHandle.getIdentifier()); 206 if (entries.isEmpty()) { 207 Log.i(TAG, packageName + " has no call screening service defined."); 208 return false; 209 } 210 211 ResolveInfo entry = entries.get(0); 212 if (entry.serviceInfo == null) { 213 Log.w(TAG, packageName + " call screening service has invalid service info"); 214 return false; 215 } 216 217 if (entry.serviceInfo.permission == null || !entry.serviceInfo.permission.equals( 218 Manifest.permission.BIND_SCREENING_SERVICE)) { 219 Log.w(TAG, "CallScreeningService must require BIND_SCREENING_SERVICE permission: " + 220 entry.serviceInfo.packageName); 221 return false; 222 } 223 224 ComponentName componentName = 225 new ComponentName(entry.serviceInfo.packageName, entry.serviceInfo.name); 226 intent.setComponent(componentName); 227 if (context.bindServiceAsUser( 228 intent, 229 serviceConnection, 230 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE 231 | Context.BIND_SCHEDULE_LIKE_TOP_APP, 232 userHandle)) { 233 Log.d(TAG,"bindServiceAsUser, found service," 234 + "waiting for it to connect to user: %s", userHandle); 235 return true; 236 } 237 238 return false; 239 } 240 } 241