blob: f708f4674031c0532aeb19770e5834e68d23067c [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.googlecode.android_scripting.facade.telephony;
import static com.googlecode.android_scripting.facade.telephony.InCallServiceImpl.HandleVoiceThreadState.TERMINATE;
import static com.googlecode.android_scripting.facade.telephony.InCallServiceImpl.HandleVoiceThreadState.RUN;
import static android.os.VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
import static android.media.AudioAttributes.CONTENT_TYPE_MUSIC;
import static android.media.AudioAttributes.USAGE_MEDIA;
import static android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION;
import static android.os.Build.VERSION;
import static android.os.Build.VERSION_CODES;
import com.googlecode.android_scripting.facade.EventFacade;
import com.googlecode.android_scripting.Log;
import android.media.AudioDeviceInfo;
import android.media.AudioAttributes;
import android.media.AudioFormat;
import android.media.AudioTrack;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.telecom.Call;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.File;
/**
* The class handles playing an audio file on the route of the telephony network during a phone
* call.
*/
public class PlayAudioInCall {
private static Thread playAudioThread = null;
private AudioDeviceInfo audioDeviceInfo;
private File audioFile;
private AudioFileInfo audioFileInfo;
private Call call;
private EventFacade eventFacade;
PlayAudioInCall(EventFacade eventFacade,
Call call,
File audioFile,
AudioDeviceInfo audioDeviceInfo) {
this.eventFacade = eventFacade;
this.call = call;
this.audioFile = audioFile;
this.audioDeviceInfo = audioDeviceInfo;
Log.d(String.format("eventFacade=%s, call=%s, audioFile=%s, audioDeviceInfo=%d",
this.eventFacade, this.call, this.audioFile, this.audioDeviceInfo.getId()));
}
boolean playAudioFile() {
if (!setupAudioFileInfo())
return false;
return playAudio();
}
private boolean playAudio() {
AudioFormat audioFormat = getAudioFormat();
Log.d(String.format("Audio format: %s", audioFormat.toString()));
AudioTrack audioTrack = getAudioTrack(audioFormat, getAudioTrackBufferSize(audioFormat));
Log.d(String.format("Audio Track: %s",audioTrack.toString()));
if (!audioTrack.setPreferredDevice(audioDeviceInfo)) {
audioTrack.release();
return false;
}
Log.d(String.format("Set the preferred audio device to %d successfully",
audioDeviceInfo.getId()));
while (audioTrack.getState() != AudioTrack.STATE_INITIALIZED)
;
Log.d(String.format("Audio track state: %s", audioTrack.getState()));
return createPlayAudioThread(audioTrack);
}
private boolean createPlayAudioThread(AudioTrack audioTrack) {
playAudioThread = new Thread(() -> {
byte[] audioRaw = new byte[512];
int readBytes;
try {
InCallServiceImpl.setPlayAudioInCallState(RUN);
InCallServiceImpl.muteCall(true);
InputStream inputStream = new FileInputStream(audioFile);
inputStream.read(audioRaw, 0, 44);
audioTrack.play();
while ((readBytes = inputStream.read(audioRaw)) != -1) {
audioTrack.write(audioRaw, 0, readBytes);
if (stopPlayAudio()) {
break;
}
}
Log.d("End Playing audio!");
inputStream.close();
audioTrack.stop();
audioTrack.release();
eventFacade.postEvent(TelephonyConstants.EventCallPlayAudioStateChanged,
new InCallServiceImpl.CallEvent<String>(InCallServiceImpl.getCallId(call),
TelephonyConstants.TELEPHONY_STATE_PLAY_AUDIO_END));
}
catch (IOException e) {
audioTrack.release();
Log.d(String.format("Failed to read audio file \"%s\"!", audioFile.getName()));
eventFacade.postEvent(TelephonyConstants.EventCallPlayAudioStateChanged,
new InCallServiceImpl.CallEvent<String>(InCallServiceImpl.getCallId(call),
TelephonyConstants.TELEPHONY_STATE_PLAY_AUDIO_FAIL));
}
finally {
InCallServiceImpl.muteCall(false);
InCallServiceImpl.setPlayAudioInCallState(TERMINATE);
}
});
playAudioThread.start();
return true;
}
private AudioTrack getAudioTrack(AudioFormat audioFormat, int bufferSize) {
return new AudioTrack.Builder().setAudioFormat(audioFormat).setBufferSizeInBytes(bufferSize)
.setAudioAttributes(getAudioAttributes()).setTransferMode(AudioTrack.MODE_STREAM).build();
}
private int getAudioTrackBufferSize(AudioFormat audioFormat) {
return AudioTrack.getMinBufferSize(
audioFormat.getSampleRate(),
audioFormat.getChannelMask(),
audioFormat.getEncoding());
}
private AudioAttributes getAudioAttributes() {
if (VERSION.SDK_INT >= VERSION_CODES.Q) {
Log.d("AudioAttributes above Android Q is used.");
return new AudioAttributes.Builder()
.setUsage(USAGE_VOICE_COMMUNICATION).build();
} else {
Log.d("AudioAttributes below Android Q is used.");
return new AudioAttributes.Builder()
.setContentType(CONTENT_TYPE_MUSIC)
.setFlags(FLAG_BYPASS_INTERRUPTION_POLICY)
.setUsage(USAGE_MEDIA).build();
}
}
private boolean setupAudioFileInfo() {
MediaExtractor extractor = new MediaExtractor();
try {
extractor.setDataSource(audioFile.getAbsolutePath());
} catch (IOException e) {
Log.d(String.format("Failed to set data source in MediaExtrator, %s", e.getMessage()));
return false;
}
extractor.selectTrack(0);
MediaFormat format = extractor.getTrackFormat(0);
audioFileInfo = AudioFileInfo.create(format.getString(MediaFormat.KEY_MIME),
format.getInteger(MediaFormat.KEY_SAMPLE_RATE),
format.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
Log.d(String.format("The media format is %s", audioFileInfo));
return true;
}
private AudioFormat getAudioFormat() {
int channelMask = audioFileInfo.channelCount() == 1 ? AudioFormat.CHANNEL_OUT_MONO
: AudioFormat.CHANNEL_OUT_STEREO;
return new AudioFormat.Builder().setChannelMask(channelMask)
.setSampleRate(audioFileInfo.sampleRate()).setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.build();
}
private boolean stopPlayAudio() {
if (InCallServiceImpl.getPlayAudioInCallState().equals(RUN)) {
return false;
}
Log.d("Stop playing audio!");
return true;
}
}