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