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