• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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