/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.telecom; import static android.telecom.CallStreamingService.STREAMING_FAILED_SENDER_BINDING_ERROR; import android.Manifest; import android.annotation.SuppressLint; import android.app.role.RoleManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.os.Bundle; import android.os.IBinder; import android.os.OutcomeReceiver; import android.os.RemoteException; import android.os.UserHandle; import android.telecom.CallException; import android.telecom.CallStreamingService; import android.telecom.StreamingCall; import android.telecom.Log; import com.android.internal.telecom.ICallStreamingService; import com.android.server.telecom.voip.ParallelTransaction; import com.android.server.telecom.voip.SerialTransaction; import com.android.server.telecom.voip.VoipCallTransaction; import com.android.server.telecom.voip.VoipCallTransactionResult; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; public class CallStreamingController extends CallsManagerListenerBase { private Call mStreamingCall; private TransactionalServiceWrapper mTransactionalServiceWrapper; private ICallStreamingService mService; private final Context mContext; private CallStreamingServiceConnection mConnection; private boolean mIsStreaming; private final Object mLock; private TelecomSystem.SyncRoot mTelecomLock; public CallStreamingController(Context context, TelecomSystem.SyncRoot telecomLock) { mLock = new Object(); mContext = context; mTelecomLock = telecomLock; } private void onConnectedInternal(Call call, TransactionalServiceWrapper wrapper, IBinder service) throws RemoteException { synchronized (mLock) { Log.i(this, "onConnectedInternal: callid=%s", call.getId()); Bundle extras = new Bundle(); extras.putString(StreamingCall.EXTRA_CALL_ID, call.getId()); mStreamingCall = call; mTransactionalServiceWrapper = wrapper; mService = ICallStreamingService.Stub.asInterface(service); mService.setStreamingCallAdapter(new StreamingCallAdapter(mTransactionalServiceWrapper, mStreamingCall, mStreamingCall.getTargetPhoneAccount().getComponentName().getPackageName())); mService.onCallStreamingStarted(new StreamingCall( mTransactionalServiceWrapper.getComponentName(), mStreamingCall.getCallerDisplayName(), mStreamingCall.getHandle(), extras)); mIsStreaming = true; } } private void resetController() { synchronized (mLock) { mStreamingCall = null; mTransactionalServiceWrapper = null; if (mConnection != null) { // Notify service streaming stopped and then unbind. try { mService.onCallStreamingStopped(); } catch (RemoteException e) { // Could not notify stop streaming; we're about to just unbind so this is // unfortunate but not the end of the world. Log.e(this, e, "resetController: failed to notify stop streaming."); } mContext.unbindService(mConnection); mConnection = null; } mService = null; mIsStreaming = false; } } public boolean isStreaming() { synchronized (mLock) { return mIsStreaming; } } public static class QueryCallStreamingTransaction extends VoipCallTransaction { private final CallsManager mCallsManager; public QueryCallStreamingTransaction(CallsManager callsManager) { super(callsManager.getLock()); mCallsManager = callsManager; } @Override public CompletableFuture processTransaction(Void v) { Log.i(this, "processTransaction"); CompletableFuture future = new CompletableFuture<>(); if (mCallsManager.getCallStreamingController().isStreaming()) { future.complete(new VoipCallTransactionResult( CallException.CODE_ERROR_UNKNOWN /* TODO:: define error b/335703584 */, "STREAMING_FAILED_ALREADY_STREAMING")); } else { future.complete(new VoipCallTransactionResult( VoipCallTransactionResult.RESULT_SUCCEED, null)); } return future; } } public static class AudioInterceptionTransaction extends VoipCallTransaction { private Call mCall; private boolean mEnterInterception; public AudioInterceptionTransaction(Call call, boolean enterInterception, TelecomSystem.SyncRoot lock) { super(lock); mCall = call; mEnterInterception = enterInterception; } @Override public CompletableFuture processTransaction(Void v) { Log.i(this, "processTransaction"); CompletableFuture future = new CompletableFuture<>(); if (mEnterInterception) { mCall.startStreaming(); } else { mCall.stopStreaming(); } future.complete(new VoipCallTransactionResult(VoipCallTransactionResult.RESULT_SUCCEED, null)); return future; } } public StreamingServiceTransaction getCallStreamingServiceTransaction(Context context, TransactionalServiceWrapper wrapper, Call call) { return new StreamingServiceTransaction(context, wrapper, call); } public class StreamingServiceTransaction extends VoipCallTransaction { public static final String MESSAGE = "STREAMING_FAILED_NO_SENDER"; private final TransactionalServiceWrapper mWrapper; private final Context mContext; private final UserHandle mUserHandle; private final Call mCall; public StreamingServiceTransaction(Context context, TransactionalServiceWrapper wrapper, Call call) { super(mTelecomLock); mWrapper = wrapper; mCall = call; mUserHandle = mCall.getAssociatedUser(); mContext = context; } @SuppressLint("LongLogTag") @Override public CompletionStage processTransaction(Void v) { Log.i(this, "processTransaction"); CompletableFuture future = new CompletableFuture<>(); RoleManager roleManager = mContext.getSystemService(RoleManager.class); PackageManager packageManager = mContext.getPackageManager(); if (roleManager == null || packageManager == null) { Log.w(this, "processTransaction: Can't find system service"); future.complete(new VoipCallTransactionResult( CallException.CODE_ERROR_UNKNOWN /* TODO:: define error b/335703584 */, MESSAGE)); return future; } List holders = roleManager.getRoleHoldersAsUser( RoleManager.ROLE_SYSTEM_CALL_STREAMING, mUserHandle); if (holders.isEmpty()) { Log.w(this, "processTransaction: Can't find streaming app"); future.complete(new VoipCallTransactionResult( CallException.CODE_ERROR_UNKNOWN /* TODO:: define error b/335703584 */, MESSAGE)); return future; } Log.i(this, "processTransaction: servicePackage=%s", holders.get(0)); Intent serviceIntent = new Intent(CallStreamingService.SERVICE_INTERFACE); serviceIntent.setPackage(holders.get(0)); List infos = packageManager.queryIntentServicesAsUser(serviceIntent, PackageManager.GET_META_DATA, mUserHandle); if (infos.isEmpty()) { Log.w(this, "processTransaction: Can't find streaming service"); future.complete(new VoipCallTransactionResult( CallException.CODE_ERROR_UNKNOWN /* TODO:: define error b/335703584 */, MESSAGE)); return future; } ServiceInfo serviceInfo = infos.get(0).serviceInfo; if (serviceInfo.permission == null || !serviceInfo.permission.equals( Manifest.permission.BIND_CALL_STREAMING_SERVICE)) { Log.w(this, "Must require BIND_CALL_STREAMING_SERVICE: " + serviceInfo.packageName); future.complete(new VoipCallTransactionResult( CallException.CODE_ERROR_UNKNOWN /* TODO:: define error b/335703584 */, MESSAGE)); return future; } Intent intent = new Intent(CallStreamingService.SERVICE_INTERFACE); intent.setComponent(serviceInfo.getComponentName()); mConnection = new CallStreamingServiceConnection(mCall, mWrapper, future); if (!mContext.bindServiceAsUser(intent, mConnection, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE | Context.BIND_SCHEDULE_LIKE_TOP_APP, mUserHandle)) { Log.w(this, "Can't bind to streaming service"); future.complete(new VoipCallTransactionResult( CallException.CODE_ERROR_UNKNOWN /* TODO:: define error b/335703584 */, "STREAMING_FAILED_SENDER_BINDING_ERROR")); } return future; } } public UnbindStreamingServiceTransaction getUnbindStreamingServiceTransaction() { return new UnbindStreamingServiceTransaction(); } public class UnbindStreamingServiceTransaction extends VoipCallTransaction { public UnbindStreamingServiceTransaction() { super(mTelecomLock); } @SuppressLint("LongLogTag") @Override public CompletionStage processTransaction(Void v) { Log.i(this, "processTransaction (unbindStreaming"); CompletableFuture future = new CompletableFuture<>(); resetController(); future.complete(new VoipCallTransactionResult(VoipCallTransactionResult.RESULT_SUCCEED, null)); return future; } } public class StartStreamingTransaction extends SerialTransaction { private Call mCall; public StartStreamingTransaction(List subTransactions, Call call, TelecomSystem.SyncRoot lock) { super(subTransactions, lock); mCall = call; } @Override public void handleTransactionFailure() { mTransactionalServiceWrapper.stopCallStreaming(mCall); } } public VoipCallTransaction getStartStreamingTransaction(CallsManager callsManager, TransactionalServiceWrapper wrapper, Call call, TelecomSystem.SyncRoot lock) { // start streaming transaction flow: // 1. make sure there's no ongoing streaming call --> bind to EXO // 2. change audio mode // 3. bind to EXO // If bind to EXO failed, add transaction for stop the streaming // create list for multiple transactions List transactions = new ArrayList<>(); transactions.add(new QueryCallStreamingTransaction(callsManager)); transactions.add(new AudioInterceptionTransaction(call, true, lock)); transactions.add(getCallStreamingServiceTransaction( callsManager.getContext(), wrapper, call)); return new StartStreamingTransaction(transactions, call, lock); } public VoipCallTransaction getStopStreamingTransaction(Call call, TelecomSystem.SyncRoot lock) { // TODO: implement this // Stop streaming transaction flow: List transactions = new ArrayList<>(); // 1. unbind to call streaming service transactions.add(getUnbindStreamingServiceTransaction()); // 2. audio route operations transactions.add(new CallStreamingController.AudioInterceptionTransaction(call, false, lock)); return new ParallelTransaction(transactions, lock); } @Override public void onCallRemoved(Call call) { if (mStreamingCall == call) { mTransactionalServiceWrapper.stopCallStreaming(call); } } @Override public void onCallStateChanged(Call call, int oldState, int newState) { // TODO: make sure we are only able to stream the one call and not switch focus to another // and have it streamed too if (mStreamingCall == call && oldState != newState) { CallStreamingStateChangeTransaction transaction = null; switch (newState) { case CallState.ACTIVE: transaction = new CallStreamingStateChangeTransaction( StreamingCall.STATE_STREAMING); break; case CallState.ON_HOLD: transaction = new CallStreamingStateChangeTransaction( StreamingCall.STATE_HOLDING); break; case CallState.DISCONNECTING: case CallState.DISCONNECTED: Log.addEvent(call, LogUtils.Events.STOP_STREAMING); transaction = new CallStreamingStateChangeTransaction( StreamingCall.STATE_DISCONNECTED); break; default: // ignore } if (transaction != null) { mTransactionalServiceWrapper.getTransactionManager().addTransaction(transaction, new OutcomeReceiver<>() { @Override public void onResult(VoipCallTransactionResult result) { // ignore } @Override public void onError(CallException exception) { Log.e(this, exception, "Exception when set call " + "streaming state to streaming app"); } }); } } } private class CallStreamingStateChangeTransaction extends VoipCallTransaction { @StreamingCall.StreamingCallState int mState; public CallStreamingStateChangeTransaction(@StreamingCall.StreamingCallState int state) { super(mTelecomLock); mState = state; } @Override public CompletionStage processTransaction(Void v) { CompletableFuture future = new CompletableFuture<>(); try { mService.onCallStreamingStateChanged(mState); future.complete(new VoipCallTransactionResult( VoipCallTransactionResult.RESULT_SUCCEED, null)); } catch (RemoteException e) { future.complete(new VoipCallTransactionResult( CallException.CODE_ERROR_UNKNOWN /* TODO:: define error b/335703584 */, "Exception when request " + "setting state to streaming app.")); } return future; } } private class CallStreamingServiceConnection implements ServiceConnection { private Call mCall; private TransactionalServiceWrapper mWrapper; private CompletableFuture mFuture; public CallStreamingServiceConnection(Call call, TransactionalServiceWrapper wrapper, CompletableFuture future) { mCall = call; mWrapper = wrapper; mFuture = future; } @Override public void onServiceConnected(ComponentName name, IBinder service) { try { Log.i(this, "onServiceConnected: " + name); onConnectedInternal(mCall, mWrapper, service); mFuture.complete(new VoipCallTransactionResult( VoipCallTransactionResult.RESULT_SUCCEED, null)); } catch (RemoteException e) { resetController(); mFuture.complete(new VoipCallTransactionResult( CallException.CODE_ERROR_UNKNOWN /* TODO:: define error b/335703584 */, StreamingServiceTransaction.MESSAGE)); } } @Override public void onServiceDisconnected(ComponentName name) { clearBinding(); } @Override public void onBindingDied(ComponentName name) { clearBinding(); } @Override public void onNullBinding(ComponentName name) { clearBinding(); } private void clearBinding() { resetController(); if (!mFuture.isDone()) { mFuture.complete(new VoipCallTransactionResult( CallException.CODE_ERROR_UNKNOWN /* TODO:: define error b/335703584 */, "STREAMING_FAILED_SENDER_BINDING_ERROR")); } else { mWrapper.onCallStreamingFailed(mCall, STREAMING_FAILED_SENDER_BINDING_ERROR); } } } }