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