blob: 20a67811516a36f8676d45b7a608d797753d34fc [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.telephony.imsmedia;
import android.hardware.radio.ims.media.IImsMediaSession;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.support.annotation.VisibleForTesting;
import android.telephony.CallQuality;
import android.telephony.ims.RtpHeaderExtension;
import android.telephony.imsmedia.AudioConfig;
import android.telephony.imsmedia.IImsAudioSession;
import android.telephony.imsmedia.IImsAudioSessionCallback;
import android.telephony.imsmedia.ImsMediaSession;
import android.telephony.imsmedia.MediaQualityStatus;
import android.telephony.imsmedia.MediaQualityThreshold;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.telephony.imsmedia.Utils.OpenSessionParams;
import java.util.List;
import java.util.stream.Collectors;
/**
* Audio session binder implementation which handles all audio session APIs
* from the VOIP applications.
*/
public final class AudioSession extends IImsAudioSession.Stub implements IMediaSession {
private static final String TAG = "AudioSession";
public static final int CMD_OPEN_SESSION = 101;
public static final int CMD_CLOSE_SESSION = 102;
public static final int CMD_MODIFY_SESSION = 103;
public static final int CMD_ADD_CONFIG = 104;
public static final int CMD_DELETE_CONFIG = 105;
public static final int CMD_CONFIRM_CONFIG = 106;
public static final int CMD_SEND_DTMF = 107;
public static final int CMD_SEND_RTP_HDR_EXTN = 108;
public static final int CMD_SET_MEDIA_QUALITY_THRESHOLD = 109;
public static final int CMD_START_DTMF = 110;
public static final int CMD_STOP_DTMF = 111;
public static final int EVENT_OPEN_SESSION_SUCCESS = 201;
public static final int EVENT_OPEN_SESSION_FAILURE = 202;
public static final int EVENT_MODIFY_SESSION_RESPONSE = 203;
public static final int EVENT_ADD_CONFIG_RESPONSE = 204;
public static final int EVENT_CONFIRM_CONFIG_RESPONSE = 205;
public static final int EVENT_FIRST_MEDIA_PACKET_IND = 206;
public static final int EVENT_RTP_HEADER_EXTENSION_IND = 207;
public static final int EVENT_MEDIA_QUALITY_STATUS_IND = 208;
public static final int EVENT_TRIGGER_ANBR_QUERY_IND = 209;
public static final int EVENT_DTMF_RECEIVED_IND = 210;
public static final int EVENT_CALL_QUALITY_CHANGE_IND = 211;
public static final int EVENT_SESSION_CLOSED = 212;
private static final int DTMF_DEFAULT_DURATION = 140;
private int mSessionId;
private AudioOffloadService mOffloadService;
private AudioOffloadListener mOffloadListener;
private IImsAudioSessionCallback mCallback;
private IImsMediaSession mHalSession;
private AudioSessionHandler mHandler;
private boolean mIsAudioOffload;
private AudioService mAudioService;
private AudioListener mAudioListener;
private AudioLocalSession mLocalSession;
AudioSession(final int sessionId, final IImsAudioSessionCallback callback) {
mSessionId = sessionId;
mCallback = callback;
mHandler = new AudioSessionHandler(Looper.getMainLooper());
if (isAudioOffload()) {
Log.d(TAG, "Initialize offload service");
mOffloadService = AudioOffloadService.getInstance();
mOffloadListener = new AudioOffloadListener(mHandler);
} else {
Log.d(TAG, "Initialize local audio service");
mAudioService = new AudioService();
mAudioListener = new AudioListener(mHandler);
mAudioService.setListener(mAudioListener);
mAudioListener.setNativeObject(mAudioService.getNativeObject());
}
}
@VisibleForTesting
AudioSession(final int sessionId,
@NonNull final IImsAudioSessionCallback callback,
@Nullable final AudioService audioService,
@Nullable final AudioLocalSession localSession,
@Nullable final AudioOffloadService offloadService,
Looper looper) {
mSessionId = sessionId;
mCallback = callback;
mHandler = new AudioSessionHandler(looper);
mAudioService = audioService;
mLocalSession = localSession;
mAudioListener = new AudioListener(mHandler);
mOffloadService = offloadService;
mOffloadListener = new AudioOffloadListener(mHandler);
}
@VisibleForTesting
void setAudioOffload(boolean isOffload) {
mIsAudioOffload = isOffload;
}
@VisibleForTesting
AudioSessionHandler getAudioSessionHandler() {
return mHandler;
}
@VisibleForTesting
AudioListener getAudioListener() {
return mAudioListener;
}
AudioOffloadListener getOffloadListener() {
return mOffloadListener;
}
@Override
public void openSession(OpenSessionParams sessionParams) {
Utils.sendMessage(mHandler, CMD_OPEN_SESSION, sessionParams);
}
@Override
public void closeSession() {
Utils.sendMessage(mHandler, CMD_CLOSE_SESSION);
}
@Override
public int getSessionId() {
return mSessionId;
}
@Override
public void modifySession(AudioConfig config) {
Log.d(TAG, "modifySession: " + config);
Utils.sendMessage(mHandler, CMD_MODIFY_SESSION, config);
}
@Override
public void addConfig(AudioConfig config) {
Log.d(TAG, "addConfig: " + config);
Utils.sendMessage(mHandler, CMD_ADD_CONFIG, config);
}
@Override
public void deleteConfig(AudioConfig config) {
Log.d(TAG, "deleteConfig: " + config);
Utils.sendMessage(mHandler, CMD_DELETE_CONFIG, config);
}
@Override
public void confirmConfig(AudioConfig config) {
Log.d(TAG, "confirmConfig: " + config);
Utils.sendMessage(mHandler, CMD_CONFIRM_CONFIG, config);
}
@Override
public void sendDtmf(char digit, int duration) {
Log.d(TAG, "sendDtmf: digit=" + digit + ",duration=" + duration);
Utils.sendMessage(mHandler, CMD_SEND_DTMF, duration, Utils.UNUSED, digit);
}
@Override
public void startDtmf(char digit) {
Log.d(TAG, "startDtmf: digit=" + digit);
Utils.sendMessage(mHandler, CMD_START_DTMF, digit);
}
@Override
public void stopDtmf() {
Log.d(TAG, "stopDtmf");
Utils.sendMessage(mHandler, CMD_STOP_DTMF);
}
@Override
public void sendHeaderExtension(List<RtpHeaderExtension> extensions) {
Log.d(TAG, "sendHeaderExtension");
Utils.sendMessage(mHandler, CMD_SEND_RTP_HDR_EXTN, extensions);
}
@Override
public void setMediaQualityThreshold(MediaQualityThreshold threshold) {
Log.d(TAG, "setMediaQualityThreshold: " + threshold);
Utils.sendMessage(mHandler, CMD_SET_MEDIA_QUALITY_THRESHOLD, threshold);
}
@Override
public void onOpenSessionSuccess(Object session) {
Log.d(TAG, "onOpenSessionSuccess");
Utils.sendMessage(mHandler, EVENT_OPEN_SESSION_SUCCESS, session);
}
@Override
public void onOpenSessionFailure(int error) {
Log.d(TAG, "onOpenSessionFailure: error=" + error);
Utils.sendMessage(mHandler, EVENT_OPEN_SESSION_FAILURE, error);
}
@Override
public void onSessionClosed() {
Log.d(TAG, "onSessionClosed");
Utils.sendMessage(mHandler, EVENT_SESSION_CLOSED);
}
private boolean isAudioOffload() {
return mIsAudioOffload;
}
/**
* Audio session message mHandler
*/
class AudioSessionHandler extends Handler {
AudioSessionHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage (Message msg) {
Log.d(TAG, "handleMessage() -" + AudioSessionHandler.this + ", " + msg.what);
switch(msg.what) {
case CMD_OPEN_SESSION:
handleOpenSession((OpenSessionParams)msg.obj);
break;
case CMD_CLOSE_SESSION:
handleCloseSession();
break;
case CMD_MODIFY_SESSION:
handleModifySession((AudioConfig)msg.obj);
break;
case CMD_ADD_CONFIG:
handleAddConfig((AudioConfig)msg.obj);
break;
case CMD_DELETE_CONFIG:
handleDeleteConfig((AudioConfig)msg.obj);
break;
case CMD_CONFIRM_CONFIG:
handleConfirmConfig((AudioConfig)msg.obj);
break;
case CMD_SEND_DTMF:
handleSendDtmf((char) msg.obj, msg.arg1);
break;
case CMD_START_DTMF:
handleStartDtmf((char) msg.obj);
break;
case CMD_STOP_DTMF:
handleStopDtmf();
break;
case CMD_SEND_RTP_HDR_EXTN:
handleSendRtpHeaderExtension((List<RtpHeaderExtension>)msg.obj);
break;
case CMD_SET_MEDIA_QUALITY_THRESHOLD:
handleSetMediaQualityThreshold((MediaQualityThreshold)msg.obj);
break;
case EVENT_OPEN_SESSION_SUCCESS:
handleOpenSuccess(msg.obj);
break;
case EVENT_OPEN_SESSION_FAILURE:
handleOpenFailure((int)msg.obj);
break;
case EVENT_SESSION_CLOSED:
handleSessionClosed();
break;
case EVENT_MODIFY_SESSION_RESPONSE:
handleModifySessionRespose((AudioConfig)msg.obj, msg.arg1);
break;
case EVENT_ADD_CONFIG_RESPONSE:
handleAddConfigResponse((AudioConfig)msg.obj, msg.arg1);
break;
case EVENT_CONFIRM_CONFIG_RESPONSE:
handleConfirmConfigResponse((AudioConfig)msg.obj, msg.arg1);
break;
case EVENT_FIRST_MEDIA_PACKET_IND:
handleFirstMediaPacketInd((AudioConfig)msg.obj);
break;
case EVENT_RTP_HEADER_EXTENSION_IND:
handleRtpHeaderExtensionInd((List<RtpHeaderExtension>)msg.obj);
break;
case EVENT_MEDIA_QUALITY_STATUS_IND:
handleNotifyMediaQualityStatus((MediaQualityStatus) msg.obj);
break;
case EVENT_TRIGGER_ANBR_QUERY_IND:
handleTriggerAnbrQuery((AudioConfig) msg.obj);
break;
case EVENT_DTMF_RECEIVED_IND:
handleDtmfReceived((char) msg.arg1, msg.arg2);
break;
case EVENT_CALL_QUALITY_CHANGE_IND:
handleCallQualityChangeInd((CallQuality) msg.obj);
break;
default:
}
}
}
private void handleOpenSession(OpenSessionParams sessionParams) {
if (isAudioOffload()) {
mOffloadService.openSession(mSessionId, sessionParams);
} else {
mAudioListener.setMediaCallback(sessionParams.getCallback());
int result = mAudioService.openSession(mSessionId, sessionParams);
if (result != ImsMediaSession.RESULT_SUCCESS) {
handleOpenFailure(result);
}
}
}
private void handleCloseSession() {
Log.d(TAG, "handleCloseSession");
if (isAudioOffload()) {
mOffloadService.closeSession(mSessionId);
} else {
mAudioService.closeSession(mSessionId);
}
}
private void handleModifySession(AudioConfig config) {
if (isAudioOffload()) {
try {
mHalSession.modifySession(Utils.convertToRtpConfig(config));
} catch (RemoteException e) {
Log.e(TAG, "modifySession : " + e);
}
} else {
mLocalSession.modifySession(config);
}
}
private void handleAddConfig(AudioConfig config) {
if (isAudioOffload()) {
try {
mHalSession.modifySession(Utils.convertToRtpConfig(config));
} catch (RemoteException e) {
Log.e(TAG, "addConfig : " + e);
}
} else {
mLocalSession.addConfig(config);
}
}
private void handleDeleteConfig(AudioConfig config) {
if (!isAudioOffload()) {
mLocalSession.deleteConfig(config);
}
}
private void handleConfirmConfig(AudioConfig config) {
if (!isAudioOffload()) {
mLocalSession.confirmConfig(config);
}
}
private void handleSendDtmf(char digit, int duration) {
if (isAudioOffload()) {
try {
mHalSession.sendDtmf(digit, duration);
} catch (RemoteException e) {
Log.e(TAG, "sendDtmf : " + e);
}
} else {
mLocalSession.sendDtmf(digit, duration);
}
}
private void handleStartDtmf(char digit) {
if (isAudioOffload()) {
try {
mHalSession.startDtmf(digit);
} catch (RemoteException e) {
Log.e(TAG, "startDtmf : " + e);
}
} else {
mLocalSession.sendDtmf(digit, DTMF_DEFAULT_DURATION);
}
}
private void handleStopDtmf() {
if (isAudioOffload()) {
try {
mHalSession.stopDtmf();
} catch (RemoteException e) {
Log.e(TAG, "stopDtmf : " + e);
}
}
}
private void handleSendRtpHeaderExtension(List<RtpHeaderExtension> extensions) {
if (isAudioOffload()) {
try {
List<android.hardware.radio.ims.media.RtpHeaderExtension>
halExtensions = extensions.stream().map(Utils::convertRtpHeaderExtension)
.collect(Collectors.toList());
mHalSession.sendHeaderExtension(halExtensions);
} catch (RemoteException e) {
Log.e(TAG, "sendHeaderExtension : " + e);
}
} else {
mLocalSession.sendHeaderExtension(extensions);
}
}
private void handleSetMediaQualityThreshold(MediaQualityThreshold threshold) {
if (isAudioOffload()) {
try {
mHalSession.setMediaQualityThreshold(Utils.convertMediaQualityThreshold(threshold));
} catch (RemoteException e) {
Log.e(TAG, "setMediaQualityThreshold: " + e);
}
} else {
mLocalSession.setMediaQualityThreshold(threshold);
}
}
private void handleOpenSuccess(Object session) {
if (session instanceof IImsMediaSession) {
try {
((IImsMediaSession)session).setListener(mOffloadListener);
} catch (RemoteException e) {
Log.e(TAG, "Failed to notify openSuccess: " + e);
}
mHalSession = (IImsMediaSession) session;
} else {
mLocalSession = (AudioLocalSession)session;
}
try {
mCallback.onOpenSessionSuccess(this);
} catch (RemoteException e) {
Log.e(TAG, "Failed to notify openSuccess: " + e);
}
}
private void handleOpenFailure(int error) {
try {
mCallback.onOpenSessionFailure(error);
} catch (RemoteException e) {
Log.e(TAG, "Failed to notify openFailure: " + e);
}
}
private void handleSessionClosed() {
try {
mCallback.onSessionClosed();
} catch (RemoteException e) {
Log.e(TAG, "Failed to notify SessionClosed: " + e);
}
}
private void handleModifySessionRespose(AudioConfig config, int error) {
try {
if (error != ImsMediaSession.RESULT_SUCCESS) {
Log.e(TAG, "modifySession failed with error: " + error);
}
mCallback.onModifySessionResponse(config, error);
} catch (RemoteException e) {
Log.e(TAG, "Failed to notify modifySessionResponse: " + e);
}
}
private void handleAddConfigResponse(AudioConfig config, int error) {
try {
mCallback.onAddConfigResponse(config, error);
} catch (RemoteException e) {
Log.e(TAG, "Failed to notify onAddConfigResponse: " + e);
}
}
private void handleConfirmConfigResponse(AudioConfig config, int error) {
try {
mCallback.onConfirmConfigResponse(config, error);
} catch (RemoteException e) {
Log.e(TAG, "Failed to notify onConfirmConfigResponse: " + e);
}
}
private void handleFirstMediaPacketInd(AudioConfig config) {
try {
mCallback.onFirstMediaPacketReceived(config);
} catch (RemoteException e) {
Log.e(TAG, "Failed to notify first media packet received indication: " + e);
}
}
private void handleRtpHeaderExtensionInd(List<RtpHeaderExtension> extensions) {
try {
mCallback.onHeaderExtensionReceived(extensions);
} catch (RemoteException e) {
Log.e(TAG, "Failed to notify RTP header extension: " + e);
}
}
private void handleNotifyMediaQualityStatus(MediaQualityStatus status) {
try {
mCallback.notifyMediaQualityStatus(status);
} catch (RemoteException e) {
Log.e(TAG, "Failed to notify media quality status: " + e);
}
}
private void handleTriggerAnbrQuery(AudioConfig config) {
try {
mCallback.triggerAnbrQuery(config);
} catch (RemoteException e) {
Log.e(TAG, "Failed to trigger ANBR query: " + e);
}
}
private void handleDtmfReceived(char dtmfDigit, int durationMs) {
try {
mCallback.onDtmfReceived(dtmfDigit, durationMs);
} catch (RemoteException e) {
Log.e(TAG, "Failed to Dtmf received: " + e);
}
}
private void handleCallQualityChangeInd(CallQuality callQuality) {
try {
mCallback.onCallQualityChanged(callQuality);
} catch (RemoteException e) {
Log.e(TAG, "Failed to notify call quality changed indication: " + e);
}
}
}