diff options
author | Pei Huang <peihuang@google.com> | 2023-03-01 03:06:36 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2023-03-01 03:06:36 +0000 |
commit | 8094b291ba305f85dbd1e7441931fa281fd1e891 (patch) | |
tree | 997b0a7371be9d9ab8b648a64e5652f84188bfb2 | |
parent | 36fd112bc08962ea2d5ae9a4131bd70cc6f1351b (diff) | |
parent | 33d46553a70089685d229386e4559ada86d37129 (diff) | |
download | sl4a-8094b291ba305f85dbd1e7441931fa281fd1e891.tar.gz |
Merge "Implement recording voice during a call." am: bb781ca970 am: 33d46553a7
Original change: https://android-review.googlesource.com/c/platform/external/sl4a/+/2458488
Change-Id: Iea9893c384c1a01db1362ff9fd4c113569eefa49
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r-- | Common/src/com/googlecode/android_scripting/facade/telephony/AudioFileInfo.java (renamed from Common/src/com/googlecode/android_scripting/facade/telephony/AutoFileInfo.java) | 0 | ||||
-rw-r--r-- | Common/src/com/googlecode/android_scripting/facade/telephony/InCallServiceImpl.java | 113 | ||||
-rw-r--r-- | Common/src/com/googlecode/android_scripting/facade/telephony/PlayAudioInCall.java | 9 | ||||
-rw-r--r-- | Common/src/com/googlecode/android_scripting/facade/telephony/RecordVoiceInCall.java | 281 | ||||
-rw-r--r-- | Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyConstants.java | 3 | ||||
-rw-r--r-- | Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyManagerFacade.java | 30 |
6 files changed, 415 insertions, 21 deletions
diff --git a/Common/src/com/googlecode/android_scripting/facade/telephony/AutoFileInfo.java b/Common/src/com/googlecode/android_scripting/facade/telephony/AudioFileInfo.java index bd5fd5ff..bd5fd5ff 100644 --- a/Common/src/com/googlecode/android_scripting/facade/telephony/AutoFileInfo.java +++ b/Common/src/com/googlecode/android_scripting/facade/telephony/AudioFileInfo.java diff --git a/Common/src/com/googlecode/android_scripting/facade/telephony/InCallServiceImpl.java b/Common/src/com/googlecode/android_scripting/facade/telephony/InCallServiceImpl.java index 35e2999e..1629217a 100644 --- a/Common/src/com/googlecode/android_scripting/facade/telephony/InCallServiceImpl.java +++ b/Common/src/com/googlecode/android_scripting/facade/telephony/InCallServiceImpl.java @@ -18,6 +18,12 @@ 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 com.googlecode.android_scripting.facade.telephony.RecordVoiceInCall.MONO_CHANNEL; +import static com.googlecode.android_scripting.facade.telephony.RecordVoiceInCall.SAMPLE_RATE_16K; +import static com.googlecode.android_scripting.facade.telephony.RecordVoiceInCall.SAMPLE_RATE_48K; +import static com.googlecode.android_scripting.facade.telephony.RecordVoiceInCall.STEREO_CHANNEL; + +import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -46,20 +52,25 @@ public class InCallServiceImpl extends InCallService { private static InCallServiceImpl sService = null; private static PlayAudioInCall playAudioInCall; + private static RecordVoiceInCall recordVoiceInCall; - // The call is to play an audio file or record voice during a call + // The call is to play an audio file or record voice. private static Call playRecordCall = null; // A telephony device to route voice through mobile telphony network private static AudioDeviceInfo audioTelephonyInfo = null; - // Indicates if the call is playing or recording audio or not - private static HandleVoiceThreadState playRecordAudioInCallState = TERMINATE; + // Indicates if the call is playing audio or not + private static HandleVoiceThreadState playAudioInCallState = TERMINATE; + // Indicates if the call is recording voice or not + private static HandleVoiceThreadState recordVoiceInCallState = TERMINATE; private static AudioManager mAudioManager = null; // The audio file is to play audio on the route of telephony network - private static File audioFile; + private static File playAudioFile; + // The audio file is to store voice wav data on the route of telephony network + private static File recordVoiceFile; public static InCallServiceImpl getService() { return sService; @@ -643,8 +654,10 @@ public class InCallServiceImpl extends InCallService { if (getCallId(call).equals(getCallId(playRecordCall))) { Log.d("Terminate the call for playing/recording."); - playRecordAudioInCallState = TERMINATE; + playAudioInCallState = TERMINATE; playRecordCall = null; + recordVoiceInCallState = TERMINATE; + recordVoiceInCall = null; } CallListener.onCallRemoved(id, call); @@ -1503,8 +1516,8 @@ public class InCallServiceImpl extends InCallService { */ public static boolean playAudioFile(String audioFileName) { Log.d(String.format("Playing audio file \"%s\"...", audioFileName)); - if (playRecordAudioInCallState.equals(RUN)) { - Log.d("Playing/Recording is ongoing!"); + if (playAudioInCallState.equals(RUN)) { + Log.d("Playing is ongoing!"); return false; } if (getService() == null) { @@ -1516,14 +1529,13 @@ public class InCallServiceImpl extends InCallService { Log.d("No Telephony AudioDeviceInfo!"); return false; } - audioFile = new File(getService().getFilesDir(), audioFileName); - if (!audioFile.exists()) { + playAudioFile = new File(getService().getFilesDir(), audioFileName); + if (!playAudioFile.exists()) { Log.d(String.format("%s not found in files folder!",audioFileName)); return false; } playAudioInCall = new PlayAudioInCall(mEventFacade, - playRecordCall, audioFile, audioTelephonyInfo); - playRecordAudioInCallState = RUN; + playRecordCall, playAudioFile, audioTelephonyInfo); return playAudioInCall.playAudioFile(); } @@ -1544,21 +1556,88 @@ public class InCallServiceImpl extends InCallService { return null; } - public static HandleVoiceThreadState getPlayRecordAudioInCallState() { - return playRecordAudioInCallState; + public static HandleVoiceThreadState getPlayAudioInCallState() { + return playAudioInCallState; } - public static void setPlayRecordAudioInCallState(HandleVoiceThreadState state) { - playRecordAudioInCallState = state; + public static void setPlayAudioInCallState(HandleVoiceThreadState state) { + playAudioInCallState = state; } public static void stopPlayAudioFile() { - if (playRecordAudioInCallState.equals(RUN) && playAudioInCall != null) { + if (playAudioInCallState.equals(RUN) && playAudioInCall != null) { Log.d("Stop playing audio successfully!"); - playRecordAudioInCallState = TERMINATE; + playAudioInCallState = TERMINATE; } } + /** + * Records voice during a phone call. + * + * The method checks the following items before creating a thread to record voice. + * <ol> + * <li>Check recoding state. If there is already a voice recording, ignore the request.</li> + * <li>Check if call has been established.</li> + * <li>Check the input sample rate and channel count to meet constraints.</li> + * <li>Check if the record wav file is created successfully.</li> + * </ol> + * @param recordWavFile indicates the wav file name of the recording voice + * @param sampleRate indicates sampling rate of the recording voice + * @param channelCount indicates voice channel number to be recorded + * @return {@code true} if voice is successfully recorded. Otherwise, {@code false} + */ + public static boolean recordVoice( + String recordWavFile, int sampleRate, int channelCount, boolean cancelNoiseEcho) { + Log.d(String.format("Recording voice to the \"%s\" file...", recordWavFile)); + if (getRecordVoiceInCallState().equals(RUN)) { + Log.d("Recording is ongoing!"); + return false; + } + if (getService() == null) { + Log.d("InCallService isn't activated yet"); + return false; + } + if (sampleRate != SAMPLE_RATE_16K && sampleRate != SAMPLE_RATE_48K) { + Log.e(String.format("Don't support sample rate: %d", sampleRate)); + return false; + } + if (channelCount != MONO_CHANNEL && channelCount != STEREO_CHANNEL) { + Log.e(String.format("Don't support channel count: %d", channelCount)); + return false; + } + recordVoiceFile = new File(getService().getFilesDir(), recordWavFile); + if (!recordVoiceFile.exists()) { + try { + Log.d(String.format("Creates a empty %s wav file to store voice data!", + recordWavFile)); + recordVoiceFile.createNewFile(); + } catch (IOException e) { + Log.e(String.format("Failed to create %s wav file!", recordWavFile)); + return false; + } + } + Log.d(String.format("The voice recording info: wav file: %s, Sampling rate: %d, channel count: %d", + recordWavFile, sampleRate, channelCount)); + recordVoiceInCall = new RecordVoiceInCall(mEventFacade, playRecordCall, recordVoiceFile, + sampleRate, channelCount, cancelNoiseEcho); + return recordVoiceInCall.recordVoice(); + } + + public static void stopRecordVoice() { + if (getRecordVoiceInCallState().equals(RUN) && playAudioInCall != null) { + Log.d("Stop recording voice successfully!"); + setRecordVoiceInCallState(TERMINATE); + } + } + + public static HandleVoiceThreadState getRecordVoiceInCallState() { + return recordVoiceInCallState; + } + + public static void setRecordVoiceInCallState(HandleVoiceThreadState state) { + recordVoiceInCallState = state; + } + public static String getCallPresentationInfoString(int presentation) { switch (presentation) { case TelecomManager.PRESENTATION_ALLOWED: diff --git a/Common/src/com/googlecode/android_scripting/facade/telephony/PlayAudioInCall.java b/Common/src/com/googlecode/android_scripting/facade/telephony/PlayAudioInCall.java index 7021c224..f708f467 100644 --- a/Common/src/com/googlecode/android_scripting/facade/telephony/PlayAudioInCall.java +++ b/Common/src/com/googlecode/android_scripting/facade/telephony/PlayAudioInCall.java @@ -90,6 +90,7 @@ public class PlayAudioInCall { byte[] audioRaw = new byte[512]; int readBytes; try { + InCallServiceImpl.setPlayAudioInCallState(RUN); InCallServiceImpl.muteCall(true); InputStream inputStream = new FileInputStream(audioFile); inputStream.read(audioRaw, 0, 44); @@ -104,8 +105,6 @@ public class PlayAudioInCall { inputStream.close(); audioTrack.stop(); audioTrack.release(); - InCallServiceImpl.setPlayRecordAudioInCallState(TERMINATE); - InCallServiceImpl.muteCall(false); eventFacade.postEvent(TelephonyConstants.EventCallPlayAudioStateChanged, new InCallServiceImpl.CallEvent<String>(InCallServiceImpl.getCallId(call), TelephonyConstants.TELEPHONY_STATE_PLAY_AUDIO_END)); @@ -117,6 +116,10 @@ public class PlayAudioInCall { new InCallServiceImpl.CallEvent<String>(InCallServiceImpl.getCallId(call), TelephonyConstants.TELEPHONY_STATE_PLAY_AUDIO_FAIL)); } + finally { + InCallServiceImpl.muteCall(false); + InCallServiceImpl.setPlayAudioInCallState(TERMINATE); + } }); playAudioThread.start(); return true; @@ -174,7 +177,7 @@ public class PlayAudioInCall { } private boolean stopPlayAudio() { - if (InCallServiceImpl.getPlayRecordAudioInCallState().equals(RUN)) { + if (InCallServiceImpl.getPlayAudioInCallState().equals(RUN)) { return false; } Log.d("Stop playing audio!"); diff --git a/Common/src/com/googlecode/android_scripting/facade/telephony/RecordVoiceInCall.java b/Common/src/com/googlecode/android_scripting/facade/telephony/RecordVoiceInCall.java new file mode 100644 index 00000000..3b4adb67 --- /dev/null +++ b/Common/src/com/googlecode/android_scripting/facade/telephony/RecordVoiceInCall.java @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2023 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.RUN; +import static com.googlecode.android_scripting.facade.telephony.InCallServiceImpl.HandleVoiceThreadState.TERMINATE; +import static java.nio.ByteOrder.LITTLE_ENDIAN; + +import com.googlecode.android_scripting.Log; +import com.googlecode.android_scripting.facade.EventFacade; +import android.telecom.Call; +import android.media.AudioFormat; +import android.media.AudioRecord; +import android.media.MediaRecorder.AudioSource; +import android.media.audiofx.AcousticEchoCanceler; +import android.media.audiofx.AudioEffect; +import android.media.audiofx.NoiseSuppressor; +import com.googlecode.android_scripting.facade.telephony.InCallServiceImpl.HandleVoiceThreadState; +import java.io.File; +import java.io.IOException; +import java.io.FileOutputStream; +import java.io.DataOutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.io.RandomAccessFile; +import java.nio.charset.StandardCharsets; + +/** + * The class handles recording voice on the route of the telephony network during a phone + * call. + */public class RecordVoiceInCall { + public static final int SAMPLE_RATE_16K = 16000; + public static final int SAMPLE_RATE_48K = 48000; + public static final int MONO_CHANNEL = 1; + public static final int STEREO_CHANNEL = 2; + private static final int PCM_16_BITS = 16; + private EventFacade eventFacade; + private Call call; + private int sampleRate; + private int channelCount; + private File recordFile; + private AudioFileInfo audioFileInfo; + private AudioFormat audioFormat; + private NoiseSuppressor noiseSuppressor; + private AcousticEchoCanceler acousticEchoCanceler; + private int minRecordAudioBufferSize; + private boolean cancelNoiseEcho; + private static Thread recordVoiceThread = null; + + RecordVoiceInCall(EventFacade eventFacade, + Call call, + File recordFile, + int sampleRate, + int channelCount, + boolean cancelNoiseEcho) { + this.eventFacade = eventFacade; + this.call = call; + this.recordFile = recordFile; + this.sampleRate = sampleRate; + this.channelCount = channelCount; + this.cancelNoiseEcho = cancelNoiseEcho; + Log.d(String.format("eventFacade=%s, call=%s, recordFile=%s, sampleRate=%d, channelCount=%d", + this.eventFacade, this.call, this.recordFile, this.sampleRate, this.channelCount)); + } + + /**Handles functional flows of voice recording and exposes to be invoked by users.*/ + boolean recordVoice() { + setRecordAudioInfo(); + setAudioFormat(); + setMinRecordAudioBufferSize(); + AudioRecord audioRecord = new AudioRecord.Builder() + .setAudioSource(AudioSource.VOICE_DOWNLINK) + .setAudioFormat(audioFormat) + .setBufferSizeInBytes(minRecordAudioBufferSize) + .build(); + if (cancelNoiseEcho) { + enableNoiseSuppressor(audioRecord); + enableAcousticEchoCanceler(audioRecord); + } + return createRecordVoiceThread(audioRecord); + } + + private void setRecordAudioInfo() { + audioFileInfo = AudioFileInfo.create("WAVE", sampleRate, channelCount); + } + + /**Configures an object of {@code AudioFormat} needed for voice recording in a call. + * The minimal info is to provide channel count and voice encoding scheme. + */ + private void setAudioFormat() { + int channelMask = audioFileInfo.channelCount() == 1 ? AudioFormat.CHANNEL_IN_MONO + : AudioFormat.CHANNEL_IN_STEREO; + audioFormat = new AudioFormat.Builder().setChannelMask(channelMask) + .setSampleRate(audioFileInfo.sampleRate()).setEncoding(AudioFormat.ENCODING_PCM_16BIT) + .build(); + Log.d(String.format( + "Audio format for recording, channel mask: %d, sample rate: %d, encoding: PCM_16bit", + channelMask, sampleRate)); + } + + /**Acquires minimal buffer size for sampling voice data.*/ + private void setMinRecordAudioBufferSize() { + minRecordAudioBufferSize = AudioRecord.getMinBufferSize(audioFormat.getSampleRate(), + audioFormat.getChannelCount(), + audioFormat.getEncoding()); + Log.d(String.format("Buffer size to record voice data: %d", minRecordAudioBufferSize)); + } + + private void enableNoiseSuppressor(AudioRecord audioRecord) { + if (NoiseSuppressor.isAvailable()) { + noiseSuppressor = NoiseSuppressor.create(audioRecord.getAudioSessionId()); + if (noiseSuppressor.setEnabled(true) != AudioEffect.SUCCESS) { + Log.w("Failed to enable noiseSuppressor!"); + return; + } + Log.d("Enable noiseSuppressor!"); + return; + } + Log.d("Noise suppressor is not available!"); + } + + private void enableAcousticEchoCanceler(AudioRecord audioRecord) { + if (AcousticEchoCanceler.isAvailable()) { + acousticEchoCanceler = AcousticEchoCanceler.create(audioRecord.getAudioSessionId()); + if (acousticEchoCanceler.setEnabled(true) != AudioEffect.SUCCESS) { + Log.w("Failed to enable AcousticEchoCanceler"); + return; + } + Log.d("Enable AcousticEchoCanceler!"); + return; + } + Log.d("AcousticEchoCanceler is not available!"); + } + + /**Creates a background thread to perform voice recording.*/ + private boolean createRecordVoiceThread(AudioRecord audioRecord) { + recordVoiceThread = new Thread(()-> { + int totalVoiceBytes = 0; + try { + InCallServiceImpl.setRecordVoiceInCallState(RUN); + InCallServiceImpl.muteCall(true); + audioRecord.startRecording(); + FileOutputStream outputStream = new FileOutputStream(recordFile); + DataOutputStream recordVoiceOutStream = new DataOutputStream(outputStream); + + writeWaveHeader(recordVoiceOutStream, totalVoiceBytes); + short[] buffer = new short[minRecordAudioBufferSize]; + + while (InCallServiceImpl.getRecordVoiceInCallState().equals(RUN)) { + int shortsRead = audioRecord.read(buffer, 0, minRecordAudioBufferSize); + totalVoiceBytes += shortsRead * 2; + for (int count = 0; count < shortsRead; count++) { + recordVoiceOutStream.writeShort(getShortByOrder(buffer[count], LITTLE_ENDIAN)); + } + } + recordVoiceOutStream.flush(); + recordVoiceOutStream.close(); + correctWaveHeaderChunkSize(totalVoiceBytes); + Log.d("End recording voice!"); + eventFacade.postEvent(TelephonyConstants.EventCallRecordVoiceStateChanged, + new InCallServiceImpl.CallEvent<String>(InCallServiceImpl.getCallId(call), + TelephonyConstants.TELEPHONY_STATE_RECORD_VOICE_END)); + + } catch (IOException e) { + Log.d(String.format("Failed to record voice to \"%s\"!", recordFile.getName())); + eventFacade.postEvent(TelephonyConstants.EventCallPlayAudioStateChanged, + new InCallServiceImpl.CallEvent<String>(InCallServiceImpl.getCallId(call), + TelephonyConstants.TELEPHONY_STATE_PLAY_AUDIO_FAIL)); + + } finally { + InCallServiceImpl.muteCall(false); + releaseRecordSources(audioRecord); + } + }); + recordVoiceThread.start(); + return true; + } + + private void releaseRecordSources(AudioRecord audioRecord) { + audioRecord.stop(); + audioRecord.release(); + if (noiseSuppressor != null) { + noiseSuppressor.release(); + } + if (acousticEchoCanceler != null) { + acousticEchoCanceler.release(); + } + InCallServiceImpl.setRecordVoiceInCallState(TERMINATE); + } + /**Creates and writes wave header to the beginning of the wave file.*/ + private void writeWaveHeader(DataOutputStream dataOutputStream, int totalBytes) + throws IOException { + /* 1-4 */ + dataOutputStream.write("RIFF".getBytes(StandardCharsets.UTF_8)); + /* 5-8 Write Chunk Size ~= the byte size of voice data + 36 */ + dataOutputStream.writeInt(getIntByOrder(totalBytes + 36, LITTLE_ENDIAN)); + /* 9-12 */ + dataOutputStream.write("WAVE".getBytes(StandardCharsets.UTF_8)); + /* 13-16 */ + dataOutputStream.write("fmt ".getBytes(StandardCharsets.UTF_8)); + /* 17-20 Writes SubChunk1Size */ + dataOutputStream.writeInt(getIntByOrder(16, LITTLE_ENDIAN)); + /* 21-22 Writes audio format, PCM: 1 */ + dataOutputStream.writeShort(getShortByOrder((short) 1, LITTLE_ENDIAN)); + /* 23-24 Writes number of channels */ + dataOutputStream.writeShort( + getShortByOrder((short) audioFileInfo.channelCount(), LITTLE_ENDIAN)); + /* 25-28 Writes sampling rate */ + dataOutputStream.writeInt( + getIntByOrder(audioFileInfo.sampleRate(), LITTLE_ENDIAN)); + /* 29-32 Writes byte rate */ + int byteRate = audioFileInfo.sampleRate() * audioFileInfo.channelCount() * PCM_16_BITS/2; + dataOutputStream.writeInt( + getIntByOrder(byteRate, LITTLE_ENDIAN)); + /* 33-34 Writes block align */ + int blockAlign = audioFileInfo.channelCount() * PCM_16_BITS / 2; + dataOutputStream.writeShort( + getShortByOrder((short) blockAlign, LITTLE_ENDIAN)); + /* 35-36 Writes PCM bits */ + dataOutputStream.writeShort( + getShortByOrder((short) PCM_16_BITS, LITTLE_ENDIAN)); + /* 37-40 */ + dataOutputStream.write("data".getBytes(StandardCharsets.UTF_8)); + /* 41-44 SubChunk2Size ~= the byte size of voice data */ + dataOutputStream.writeInt( + getIntByOrder(totalBytes, LITTLE_ENDIAN)); + } + + private void correctWaveHeaderChunkSize(int totalVoiceBytes) throws IOException { + RandomAccessFile randomVoiceAccessFile = new RandomAccessFile(recordFile, "rw"); + writeWaveHeaderChunkSize(randomVoiceAccessFile, totalVoiceBytes); + writeWaveHeaderSubChunk2Size(randomVoiceAccessFile, totalVoiceBytes); + randomVoiceAccessFile.close(); + } + + /**A wav file consists of two chunks. The first chunk is the header data which describes + * the sample rate, channel count, the size of voice data and so on. See {@code #writeWaveHeader}. + */ + private void writeWaveHeaderChunkSize(RandomAccessFile randomAccessFile, int totalVoiceBytes) + throws IOException { + randomAccessFile.seek(4); + randomAccessFile.write(intToBytes(totalVoiceBytes + 36, LITTLE_ENDIAN), 0, 4); + } + + /**A wav file consists of two chunks. The second chunk is the voice data.*/ + private void writeWaveHeaderSubChunk2Size(RandomAccessFile randomAccessFile, int totalVoiceBytes) + throws IOException { + randomAccessFile.seek(40); + randomAccessFile.write(intToBytes(totalVoiceBytes, LITTLE_ENDIAN), 0, 4); + } + + /**A short type of integer can be represented with little endian or big endian.*/ + private short getShortByOrder(short value, ByteOrder byteOrder) { + byte[] bytes = ByteBuffer.allocate(2).putShort(value).array(); + return ByteBuffer.wrap(bytes).order(byteOrder).getShort(); + } + + /**Converts integer to byte array.*/ + private byte[] intToBytes(int value, ByteOrder byteOrder) { + return ByteBuffer.allocate(4).order(byteOrder).putInt(value).array(); + } + + /**An integer type of integer can be represented with little endian or big endian.*/ + private int getIntByOrder(int value, ByteOrder byteOrder) { + byte[] bytes = ByteBuffer.allocate(4).putInt(value).array(); + return ByteBuffer.wrap(bytes).order(byteOrder).getInt(); + } +} diff --git a/Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyConstants.java b/Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyConstants.java index 46755dfe..591542f6 100644 --- a/Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyConstants.java +++ b/Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyConstants.java @@ -246,6 +246,8 @@ public class TelephonyConstants { public static final String TELEPHONY_STATE_UNKNOWN = "UNKNOWN"; public static final String TELEPHONY_STATE_PLAY_AUDIO_END = "PLAYAUDIOEND"; public static final String TELEPHONY_STATE_PLAY_AUDIO_FAIL = "PLAYAUDIOFAIL"; + public static final String TELEPHONY_STATE_RECORD_VOICE_END = "RECORDVOICEEND"; + public static final String TELEPHONY_STATE_RECORD_VOICE_FAIL = "RECORDVOICEFAIL"; /** * Constant for TTY Mode @@ -404,6 +406,7 @@ public class TelephonyConstants { public static final String EventMessageWaitingIndicatorChanged = "MessageWaitingIndicatorChanged"; public static final String EventPhysicalChannelConfigChanged = "PhysicalChannelConfigChanged"; public static final String EventCallPlayAudioStateChanged = "CallPlayAudioStateChanged"; + public static final String EventCallRecordVoiceStateChanged = "CallRecordVoiceStateChanged"; /** * Constants for OnStartTetheringCallback diff --git a/Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyManagerFacade.java b/Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyManagerFacade.java index 3cda123c..ac6f9e74 100644 --- a/Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyManagerFacade.java +++ b/Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyManagerFacade.java @@ -1636,7 +1636,8 @@ public class TelephonyManagerFacade extends RpcReceiver { */ @Rpc(description = "Plays the specified audio file during a phone call") public boolean telephonyPlayAudioFile( - @RpcParameter(name = "audioFileName") String audioFileName) { + @RpcParameter(name = "audioFileName", description = "the audio file in the app's files folder") + String audioFileName) { Log.d(String.format("Playing audio file \"%s\"...", audioFileName)); InCallServiceImpl.setEventFacade(mEventFacade); return InCallServiceImpl.playAudioFile(audioFileName); @@ -1649,6 +1650,33 @@ public class TelephonyManagerFacade extends RpcReceiver { } /** + * Records voice and writes to a wav file specified by {@code recordFileName} + * during a phone call. + * + * @return {@code true} if voice is successfully recorded + */ + @Rpc(description = "Records voice and writes to a wav file during a phone call") + public boolean telephonyRecordVoice( + @RpcParameter(name = "recordFileName", description = "The recorded voice file name") + String recordFileName, + @RpcParameter(name = "sampleRate", description = "sampling rate of voice data") + @RpcDefault("16000") Integer sampleRate, + @RpcParameter(name = "channelCount", description = "channel number of voice to record") + @RpcDefault("1") Integer channelCount, + @RpcParameter(name = "cancelNoiseEcho", description = "enable echo canceler and noise suppressor") + @RpcDefault("false") Boolean cancelNoiseEcho) { + Log.d(String.format("Recording voice to the \"%s\" file...", recordFileName)); + InCallServiceImpl.setEventFacade(mEventFacade); + return InCallServiceImpl.recordVoice(recordFileName, sampleRate, channelCount, cancelNoiseEcho); + } + + /** Stops recording voice during a phone call.*/ + @Rpc(description = "Stops recording voice during a call.") + public void telephonyStopRecordVoice() { + InCallServiceImpl.stopRecordVoice(); + } + + /** * Get the list of Forbidden PLMNs stored on the USIM * profile of the SIM for the default subscription. */ |