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