• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 static android.telecom.CallStreamingService.STREAMING_FAILED_SENDER_BINDING_ERROR;
20 
21 import android.Manifest;
22 import android.annotation.SuppressLint;
23 import android.app.role.RoleManager;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.ServiceConnection;
28 import android.content.pm.PackageManager;
29 import android.content.pm.ResolveInfo;
30 import android.content.pm.ServiceInfo;
31 import android.os.Bundle;
32 import android.os.IBinder;
33 import android.os.OutcomeReceiver;
34 import android.os.RemoteException;
35 import android.os.UserHandle;
36 import android.telecom.CallException;
37 import android.telecom.CallStreamingService;
38 import android.telecom.StreamingCall;
39 import android.telecom.Log;
40 
41 import com.android.internal.telecom.ICallStreamingService;
42 import com.android.server.telecom.voip.ParallelTransaction;
43 import com.android.server.telecom.voip.SerialTransaction;
44 import com.android.server.telecom.voip.VoipCallTransaction;
45 import com.android.server.telecom.voip.VoipCallTransactionResult;
46 
47 import java.util.ArrayList;
48 import java.util.List;
49 import java.util.concurrent.CompletableFuture;
50 import java.util.concurrent.CompletionStage;
51 
52 public class CallStreamingController extends CallsManagerListenerBase {
53     private Call mStreamingCall;
54     private TransactionalServiceWrapper mTransactionalServiceWrapper;
55     private ICallStreamingService mService;
56     private final Context mContext;
57     private CallStreamingServiceConnection mConnection;
58     private boolean mIsStreaming;
59     private final Object mLock;
60     private TelecomSystem.SyncRoot mTelecomLock;
61 
CallStreamingController(Context context, TelecomSystem.SyncRoot telecomLock)62     public CallStreamingController(Context context, TelecomSystem.SyncRoot telecomLock) {
63         mLock = new Object();
64         mContext = context;
65         mTelecomLock = telecomLock;
66     }
67 
onConnectedInternal(Call call, TransactionalServiceWrapper wrapper, IBinder service)68     private void onConnectedInternal(Call call, TransactionalServiceWrapper wrapper,
69             IBinder service) throws RemoteException {
70         synchronized (mLock) {
71             Log.i(this, "onConnectedInternal: callid=%s", call.getId());
72             Bundle extras = new Bundle();
73             extras.putString(StreamingCall.EXTRA_CALL_ID, call.getId());
74             mStreamingCall = call;
75             mTransactionalServiceWrapper = wrapper;
76             mService = ICallStreamingService.Stub.asInterface(service);
77             mService.setStreamingCallAdapter(new StreamingCallAdapter(mTransactionalServiceWrapper,
78                     mStreamingCall,
79                     mStreamingCall.getTargetPhoneAccount().getComponentName().getPackageName()));
80             mService.onCallStreamingStarted(new StreamingCall(
81                     mTransactionalServiceWrapper.getComponentName(),
82                     mStreamingCall.getCallerDisplayName(),
83                     mStreamingCall.getHandle(), extras));
84             mIsStreaming = true;
85         }
86     }
87 
resetController()88     private void resetController() {
89         synchronized (mLock) {
90             mStreamingCall = null;
91             mTransactionalServiceWrapper = null;
92             if (mConnection != null) {
93                 // Notify service streaming stopped and then unbind.
94                 try {
95                     mService.onCallStreamingStopped();
96                 } catch (RemoteException e) {
97                     // Could not notify stop streaming; we're about to just unbind so this is
98                     // unfortunate but not the end of the world.
99                     Log.e(this, e, "resetController: failed to notify stop streaming.");
100                 }
101                 mContext.unbindService(mConnection);
102                 mConnection = null;
103             }
104             mService = null;
105             mIsStreaming = false;
106         }
107     }
108 
isStreaming()109     public boolean isStreaming() {
110         synchronized (mLock) {
111             return mIsStreaming;
112         }
113     }
114 
115     public static class QueryCallStreamingTransaction extends VoipCallTransaction {
116         private final CallsManager mCallsManager;
117 
QueryCallStreamingTransaction(CallsManager callsManager)118         public QueryCallStreamingTransaction(CallsManager callsManager) {
119             super(callsManager.getLock());
120             mCallsManager = callsManager;
121         }
122 
123         @Override
processTransaction(Void v)124         public CompletableFuture<VoipCallTransactionResult> processTransaction(Void v) {
125             Log.i(this, "processTransaction");
126             CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
127 
128             if (mCallsManager.getCallStreamingController().isStreaming()) {
129                 future.complete(new VoipCallTransactionResult(
130                         VoipCallTransactionResult.RESULT_FAILED,
131                         "STREAMING_FAILED_ALREADY_STREAMING"));
132             } else {
133                 future.complete(new VoipCallTransactionResult(
134                         VoipCallTransactionResult.RESULT_SUCCEED, null));
135             }
136 
137             return future;
138         }
139     }
140 
141     public static class AudioInterceptionTransaction extends VoipCallTransaction {
142         private Call mCall;
143         private boolean mEnterInterception;
144 
AudioInterceptionTransaction(Call call, boolean enterInterception, TelecomSystem.SyncRoot lock)145         public AudioInterceptionTransaction(Call call, boolean enterInterception,
146                 TelecomSystem.SyncRoot lock) {
147             super(lock);
148             mCall = call;
149             mEnterInterception = enterInterception;
150         }
151 
152         @Override
processTransaction(Void v)153         public CompletableFuture<VoipCallTransactionResult> processTransaction(Void v) {
154             Log.i(this, "processTransaction");
155             CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
156 
157             if (mEnterInterception) {
158                 mCall.startStreaming();
159             } else {
160                 mCall.stopStreaming();
161             }
162             future.complete(new VoipCallTransactionResult(VoipCallTransactionResult.RESULT_SUCCEED,
163                     null));
164             return future;
165         }
166     }
167 
getCallStreamingServiceTransaction(Context context, TransactionalServiceWrapper wrapper, Call call)168     public StreamingServiceTransaction getCallStreamingServiceTransaction(Context context,
169             TransactionalServiceWrapper wrapper, Call call) {
170         return new StreamingServiceTransaction(context, wrapper, call);
171     }
172 
173     public class StreamingServiceTransaction extends VoipCallTransaction {
174         public static final String MESSAGE = "STREAMING_FAILED_NO_SENDER";
175         private final TransactionalServiceWrapper mWrapper;
176         private final Context mContext;
177         private final UserHandle mUserHandle;
178         private final Call mCall;
179 
StreamingServiceTransaction(Context context, TransactionalServiceWrapper wrapper, Call call)180         public StreamingServiceTransaction(Context context, TransactionalServiceWrapper wrapper,
181                 Call call) {
182             super(mTelecomLock);
183             mWrapper = wrapper;
184             mCall = call;
185             mUserHandle = mCall.getAssociatedUser();
186             mContext = context;
187         }
188 
189         @SuppressLint("LongLogTag")
190         @Override
processTransaction(Void v)191         public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
192             Log.i(this, "processTransaction");
193             CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
194             RoleManager roleManager = mContext.getSystemService(RoleManager.class);
195             PackageManager packageManager = mContext.getPackageManager();
196             if (roleManager == null || packageManager == null) {
197                 Log.w(this, "processTransaction: Can't find system service");
198                 future.complete(new VoipCallTransactionResult(
199                         VoipCallTransactionResult.RESULT_FAILED, MESSAGE));
200                 return future;
201             }
202 
203             List<String> holders = roleManager.getRoleHoldersAsUser(
204                     RoleManager.ROLE_SYSTEM_CALL_STREAMING, mUserHandle);
205             if (holders.isEmpty()) {
206                 Log.w(this, "processTransaction: Can't find streaming app");
207                 future.complete(new VoipCallTransactionResult(
208                         VoipCallTransactionResult.RESULT_FAILED, MESSAGE));
209                 return future;
210             }
211             Log.i(this, "processTransaction: servicePackage=%s", holders.get(0));
212             Intent serviceIntent = new Intent(CallStreamingService.SERVICE_INTERFACE);
213             serviceIntent.setPackage(holders.get(0));
214             List<ResolveInfo> infos = packageManager.queryIntentServicesAsUser(serviceIntent,
215                     PackageManager.GET_META_DATA, mUserHandle);
216             if (infos.isEmpty()) {
217                 Log.w(this, "processTransaction: Can't find streaming service");
218                 future.complete(new VoipCallTransactionResult(
219                         VoipCallTransactionResult.RESULT_FAILED, MESSAGE));
220                 return future;
221             }
222 
223             ServiceInfo serviceInfo = infos.get(0).serviceInfo;
224 
225             if (serviceInfo.permission == null || !serviceInfo.permission.equals(
226                     Manifest.permission.BIND_CALL_STREAMING_SERVICE)) {
227                 Log.w(this, "Must require BIND_CALL_STREAMING_SERVICE: " +
228                         serviceInfo.packageName);
229                 future.complete(new VoipCallTransactionResult(
230                         VoipCallTransactionResult.RESULT_FAILED, MESSAGE));
231                 return future;
232             }
233             Intent intent = new Intent(CallStreamingService.SERVICE_INTERFACE);
234             intent.setComponent(serviceInfo.getComponentName());
235 
236             mConnection = new CallStreamingServiceConnection(mCall, mWrapper, future);
237             if (!mContext.bindServiceAsUser(intent, mConnection, Context.BIND_AUTO_CREATE
238                     | Context.BIND_FOREGROUND_SERVICE
239                     | Context.BIND_SCHEDULE_LIKE_TOP_APP, mUserHandle)) {
240                 Log.w(this, "Can't bind to streaming service");
241                 future.complete(new VoipCallTransactionResult(
242                         VoipCallTransactionResult.RESULT_FAILED,
243                         "STREAMING_FAILED_SENDER_BINDING_ERROR"));
244             }
245             return future;
246         }
247     }
248 
getUnbindStreamingServiceTransaction()249     public UnbindStreamingServiceTransaction getUnbindStreamingServiceTransaction() {
250         return new UnbindStreamingServiceTransaction();
251     }
252 
253     public class UnbindStreamingServiceTransaction extends VoipCallTransaction {
UnbindStreamingServiceTransaction()254         public UnbindStreamingServiceTransaction() {
255             super(mTelecomLock);
256         }
257 
258         @SuppressLint("LongLogTag")
259         @Override
processTransaction(Void v)260         public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
261             Log.i(this, "processTransaction (unbindStreaming");
262             CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
263 
264             resetController();
265             future.complete(new VoipCallTransactionResult(VoipCallTransactionResult.RESULT_SUCCEED,
266                     null));
267             return future;
268         }
269     }
270 
271     public class StartStreamingTransaction extends SerialTransaction {
272         private Call mCall;
273 
StartStreamingTransaction(List<VoipCallTransaction> subTransactions, Call call, TelecomSystem.SyncRoot lock)274         public StartStreamingTransaction(List<VoipCallTransaction> subTransactions, Call call,
275                 TelecomSystem.SyncRoot lock) {
276             super(subTransactions, lock);
277             mCall = call;
278         }
279 
280         @Override
handleTransactionFailure()281         public void handleTransactionFailure() {
282             mTransactionalServiceWrapper.stopCallStreaming(mCall);
283         }
284     }
285 
getStartStreamingTransaction(CallsManager callsManager, TransactionalServiceWrapper wrapper, Call call, TelecomSystem.SyncRoot lock)286     public VoipCallTransaction getStartStreamingTransaction(CallsManager callsManager,
287             TransactionalServiceWrapper wrapper, Call call, TelecomSystem.SyncRoot lock) {
288         // start streaming transaction flow:
289         //     1. make sure there's no ongoing streaming call --> bind to EXO
290         //     2. change audio mode
291         //     3. bind to EXO
292         // If bind to EXO failed, add transaction for stop the streaming
293 
294         // create list for multiple transactions
295         List<VoipCallTransaction> transactions = new ArrayList<>();
296         transactions.add(new QueryCallStreamingTransaction(callsManager));
297         transactions.add(new AudioInterceptionTransaction(call, true, lock));
298         transactions.add(getCallStreamingServiceTransaction(
299                 callsManager.getContext(), wrapper, call));
300         return new StartStreamingTransaction(transactions, call, lock);
301     }
302 
getStopStreamingTransaction(Call call, TelecomSystem.SyncRoot lock)303     public VoipCallTransaction getStopStreamingTransaction(Call call, TelecomSystem.SyncRoot lock) {
304         // TODO: implement this
305         // Stop streaming transaction flow:
306         List<VoipCallTransaction> transactions = new ArrayList<>();
307 
308         // 1. unbind to call streaming service
309         transactions.add(getUnbindStreamingServiceTransaction());
310         // 2. audio route operations
311         transactions.add(new CallStreamingController.AudioInterceptionTransaction(call,
312                 false, lock));
313         return new ParallelTransaction(transactions, lock);
314     }
315 
316     @Override
onCallRemoved(Call call)317     public void onCallRemoved(Call call) {
318         if (mStreamingCall == call) {
319             mTransactionalServiceWrapper.stopCallStreaming(call);
320         }
321     }
322 
323     @Override
onCallStateChanged(Call call, int oldState, int newState)324     public void onCallStateChanged(Call call, int oldState, int newState) {
325         // TODO: make sure we are only able to stream the one call and not switch focus to another
326         // and have it streamed too
327         if (mStreamingCall == call && oldState != newState) {
328             CallStreamingStateChangeTransaction transaction = null;
329             switch (newState) {
330                 case CallState.ACTIVE:
331                     transaction = new CallStreamingStateChangeTransaction(
332                             StreamingCall.STATE_STREAMING);
333                     break;
334                 case CallState.ON_HOLD:
335                     transaction = new CallStreamingStateChangeTransaction(
336                             StreamingCall.STATE_HOLDING);
337                     break;
338                 case CallState.DISCONNECTING:
339                 case CallState.DISCONNECTED:
340                     Log.addEvent(call, LogUtils.Events.STOP_STREAMING);
341                     transaction = new CallStreamingStateChangeTransaction(
342                             StreamingCall.STATE_DISCONNECTED);
343                     break;
344                 default:
345                     // ignore
346             }
347             if (transaction != null) {
348                 mTransactionalServiceWrapper.getTransactionManager().addTransaction(transaction,
349                         new OutcomeReceiver<>() {
350                             @Override
351                             public void onResult(VoipCallTransactionResult result) {
352                                 // ignore
353                             }
354 
355                             @Override
356                             public void onError(CallException exception) {
357                                 Log.e(this, exception, "Exception when set call "
358                                         + "streaming state to streaming app");
359                             }
360                         });
361             }
362         }
363     }
364 
365     private class CallStreamingStateChangeTransaction extends VoipCallTransaction {
366         @StreamingCall.StreamingCallState int mState;
367 
CallStreamingStateChangeTransaction(@treamingCall.StreamingCallState int state)368         public CallStreamingStateChangeTransaction(@StreamingCall.StreamingCallState int state) {
369             super(mTelecomLock);
370             mState = state;
371         }
372 
373         @Override
processTransaction(Void v)374         public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
375             CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
376             try {
377                 mService.onCallStreamingStateChanged(mState);
378                 future.complete(new VoipCallTransactionResult(
379                         VoipCallTransactionResult.RESULT_SUCCEED, null));
380             } catch (RemoteException e) {
381                 future.complete(new VoipCallTransactionResult(
382                         VoipCallTransactionResult.RESULT_FAILED, "Exception when request "
383                         + "setting state to streaming app."));
384             }
385             return future;
386         }
387     }
388 
389     private class CallStreamingServiceConnection implements
390             ServiceConnection {
391         private Call mCall;
392         private TransactionalServiceWrapper mWrapper;
393         private CompletableFuture<VoipCallTransactionResult> mFuture;
394 
CallStreamingServiceConnection(Call call, TransactionalServiceWrapper wrapper, CompletableFuture<VoipCallTransactionResult> future)395         public CallStreamingServiceConnection(Call call, TransactionalServiceWrapper wrapper,
396                 CompletableFuture<VoipCallTransactionResult> future) {
397             mCall = call;
398             mWrapper = wrapper;
399             mFuture = future;
400         }
401 
402         @Override
onServiceConnected(ComponentName name, IBinder service)403         public void onServiceConnected(ComponentName name, IBinder service) {
404             try {
405                 Log.i(this, "onServiceConnected: " + name);
406                 onConnectedInternal(mCall, mWrapper, service);
407                 mFuture.complete(new VoipCallTransactionResult(
408                         VoipCallTransactionResult.RESULT_SUCCEED, null));
409             } catch (RemoteException e) {
410                 resetController();
411                 mFuture.complete(new VoipCallTransactionResult(
412                         VoipCallTransactionResult.RESULT_FAILED,
413                         StreamingServiceTransaction.MESSAGE));
414             }
415         }
416 
417         @Override
onServiceDisconnected(ComponentName name)418         public void onServiceDisconnected(ComponentName name) {
419             clearBinding();
420         }
421 
422         @Override
onBindingDied(ComponentName name)423         public void onBindingDied(ComponentName name) {
424             clearBinding();
425         }
426 
427         @Override
onNullBinding(ComponentName name)428         public void onNullBinding(ComponentName name) {
429             clearBinding();
430         }
431 
clearBinding()432         private void clearBinding() {
433             resetController();
434             if (!mFuture.isDone()) {
435                 mFuture.complete(new VoipCallTransactionResult(
436                         VoipCallTransactionResult.RESULT_FAILED,
437                         "STREAMING_FAILED_SENDER_BINDING_ERROR"));
438             } else {
439                 mWrapper.onCallStreamingFailed(mCall, STREAMING_FAILED_SENDER_BINDING_ERROR);
440             }
441         }
442     }
443 }
444