blob: 1323633fca080104a905eacd7a2c4869f09f6b2c [file] [log] [blame]
/*
* 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<VoipCallTransactionResult> processTransaction(Void v) {
Log.i(this, "processTransaction");
CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
if (mCallsManager.getCallStreamingController().isStreaming()) {
future.complete(new VoipCallTransactionResult(
VoipCallTransactionResult.RESULT_FAILED,
"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<VoipCallTransactionResult> processTransaction(Void v) {
Log.i(this, "processTransaction");
CompletableFuture<VoipCallTransactionResult> 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<VoipCallTransactionResult> processTransaction(Void v) {
Log.i(this, "processTransaction");
CompletableFuture<VoipCallTransactionResult> 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(
VoipCallTransactionResult.RESULT_FAILED, MESSAGE));
return future;
}
List<String> 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(
VoipCallTransactionResult.RESULT_FAILED, 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<ResolveInfo> 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(
VoipCallTransactionResult.RESULT_FAILED, 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(
VoipCallTransactionResult.RESULT_FAILED, 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(
VoipCallTransactionResult.RESULT_FAILED,
"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<VoipCallTransactionResult> processTransaction(Void v) {
Log.i(this, "processTransaction (unbindStreaming");
CompletableFuture<VoipCallTransactionResult> 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<VoipCallTransaction> 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<VoipCallTransaction> 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<VoipCallTransaction> 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<VoipCallTransactionResult> processTransaction(Void v) {
CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
try {
mService.onCallStreamingStateChanged(mState);
future.complete(new VoipCallTransactionResult(
VoipCallTransactionResult.RESULT_SUCCEED, null));
} catch (RemoteException e) {
future.complete(new VoipCallTransactionResult(
VoipCallTransactionResult.RESULT_FAILED, "Exception when request "
+ "setting state to streaming app."));
}
return future;
}
}
private class CallStreamingServiceConnection implements
ServiceConnection {
private Call mCall;
private TransactionalServiceWrapper mWrapper;
private CompletableFuture<VoipCallTransactionResult> mFuture;
public CallStreamingServiceConnection(Call call, TransactionalServiceWrapper wrapper,
CompletableFuture<VoipCallTransactionResult> 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(
VoipCallTransactionResult.RESULT_FAILED,
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(
VoipCallTransactionResult.RESULT_FAILED,
"STREAMING_FAILED_SENDER_BINDING_ERROR"));
} else {
mWrapper.onCallStreamingFailed(mCall, STREAMING_FAILED_SENDER_BINDING_ERROR);
}
}
}
}