diff --git a/core/java/android/speech/tts/FileSynthesisCallback.java b/core/java/android/speech/tts/FileSynthesisCallback.java index 3e33e8e4b5270..ab8f82f4567d3 100644 --- a/core/java/android/speech/tts/FileSynthesisCallback.java +++ b/core/java/android/speech/tts/FileSynthesisCallback.java @@ -20,10 +20,12 @@ import android.os.FileUtils; import android.util.Log; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.nio.channels.FileChannel; /** * Speech synthesis request that writes the audio to a WAV file. @@ -39,16 +41,19 @@ class FileSynthesisCallback extends AbstractSynthesisCallback { private static final short WAV_FORMAT_PCM = 0x0001; private final Object mStateLock = new Object(); - private final File mFileName; + private int mSampleRateInHz; private int mAudioFormat; private int mChannelCount; - private RandomAccessFile mFile; + + private FileChannel mFileChannel; + + private boolean mStarted = false; private boolean mStopped = false; private boolean mDone = false; - FileSynthesisCallback(File fileName) { - mFileName = fileName; + FileSynthesisCallback(FileChannel fileChannel) { + mFileChannel = fileChannel; } @Override @@ -63,54 +68,23 @@ class FileSynthesisCallback extends AbstractSynthesisCallback { * Must be called while holding the monitor on {@link #mStateLock}. */ private void cleanUp() { - closeFileAndWidenPermissions(); - if (mFile != null) { - mFileName.delete(); - } + closeFile(); } /** * Must be called while holding the monitor on {@link #mStateLock}. */ - private void closeFileAndWidenPermissions() { + private void closeFile() { try { - if (mFile != null) { - mFile.close(); - mFile = null; + if (mFileChannel != null) { + mFileChannel.close(); + mFileChannel = null; } } catch (IOException ex) { - Log.e(TAG, "Failed to close " + mFileName + ": " + ex); - } - - try { - // Make the written file readable and writeable by everyone. - // This allows the app that requested synthesis to read the file. - // - // Note that the directory this file was written must have already - // been world writeable in order it to have been - // written to in the first place. - FileUtils.setPermissions(mFileName.getAbsolutePath(), 0666, -1, -1); //-rw-rw-rw - } catch (SecurityException se) { - Log.e(TAG, "Security exception setting rw permissions on : " + mFileName); + Log.e(TAG, "Failed to close output file descriptor", ex); } } - /** - * Checks whether a given file exists, and deletes it if it does. - */ - private boolean maybeCleanupExistingFile(File file) { - if (file.exists()) { - Log.v(TAG, "File " + file + " exists, deleting."); - if (!file.delete()) { - Log.e(TAG, "Failed to delete " + file); - return false; - } - } - - return true; - } - - @Override public int getMaxBufferSize() { return MAX_AUDIO_BUFFER_SIZE; @@ -132,25 +106,20 @@ class FileSynthesisCallback extends AbstractSynthesisCallback { if (DBG) Log.d(TAG, "Request has been aborted."); return TextToSpeech.ERROR; } - if (mFile != null) { + if (mStarted) { cleanUp(); throw new IllegalArgumentException("FileSynthesisRequest.start() called twice"); } - - if (!maybeCleanupExistingFile(mFileName)) { - return TextToSpeech.ERROR; - } - + mStarted = true; mSampleRateInHz = sampleRateInHz; mAudioFormat = audioFormat; mChannelCount = channelCount; + try { - mFile = new RandomAccessFile(mFileName, "rw"); - // Reserve space for WAV header - mFile.write(new byte[WAV_HEADER_LENGTH]); + mFileChannel.write(ByteBuffer.allocate(WAV_HEADER_LENGTH)); return TextToSpeech.SUCCESS; } catch (IOException ex) { - Log.e(TAG, "Failed to open " + mFileName + ": " + ex); + Log.e(TAG, "Failed to write wav header to output file descriptor" + ex); cleanUp(); return TextToSpeech.ERROR; } @@ -168,15 +137,15 @@ class FileSynthesisCallback extends AbstractSynthesisCallback { if (DBG) Log.d(TAG, "Request has been aborted."); return TextToSpeech.ERROR; } - if (mFile == null) { + if (mFileChannel == null) { Log.e(TAG, "File not open"); return TextToSpeech.ERROR; } try { - mFile.write(buffer, offset, length); + mFileChannel.write(ByteBuffer.wrap(buffer, offset, length)); return TextToSpeech.SUCCESS; } catch (IOException ex) { - Log.e(TAG, "Failed to write to " + mFileName + ": " + ex); + Log.e(TAG, "Failed to write to output file descriptor", ex); cleanUp(); return TextToSpeech.ERROR; } @@ -197,21 +166,21 @@ class FileSynthesisCallback extends AbstractSynthesisCallback { if (DBG) Log.d(TAG, "Request has been aborted."); return TextToSpeech.ERROR; } - if (mFile == null) { + if (mFileChannel == null) { Log.e(TAG, "File not open"); return TextToSpeech.ERROR; } try { // Write WAV header at start of file - mFile.seek(0); - int dataLength = (int) (mFile.length() - WAV_HEADER_LENGTH); - mFile.write( + mFileChannel.position(0); + int dataLength = (int) (mFileChannel.size() - WAV_HEADER_LENGTH); + mFileChannel.write( makeWavHeader(mSampleRateInHz, mAudioFormat, mChannelCount, dataLength)); - closeFileAndWidenPermissions(); + closeFile(); mDone = true; return TextToSpeech.SUCCESS; } catch (IOException ex) { - Log.e(TAG, "Failed to write to " + mFileName + ": " + ex); + Log.e(TAG, "Failed to write to output file descriptor", ex); cleanUp(); return TextToSpeech.ERROR; } @@ -226,7 +195,7 @@ class FileSynthesisCallback extends AbstractSynthesisCallback { } } - private byte[] makeWavHeader(int sampleRateInHz, int audioFormat, int channelCount, + private ByteBuffer makeWavHeader(int sampleRateInHz, int audioFormat, int channelCount, int dataLength) { // TODO: is AudioFormat.ENCODING_DEFAULT always the same as ENCODING_PCM_16BIT? int sampleSizeInBytes = (audioFormat == AudioFormat.ENCODING_PCM_8BIT ? 1 : 2); @@ -251,8 +220,9 @@ class FileSynthesisCallback extends AbstractSynthesisCallback { header.putShort(bitsPerSample); header.put(new byte[]{ 'd', 'a', 't', 'a' }); header.putInt(dataLength); + header.flip(); - return headerBuf; + return header; } } diff --git a/core/java/android/speech/tts/ITextToSpeechService.aidl b/core/java/android/speech/tts/ITextToSpeechService.aidl index 6982029ad4f8a..b7bc70c7cae09 100644 --- a/core/java/android/speech/tts/ITextToSpeechService.aidl +++ b/core/java/android/speech/tts/ITextToSpeechService.aidl @@ -18,6 +18,7 @@ package android.speech.tts; import android.net.Uri; import android.os.Bundle; +import android.os.ParcelFileDescriptor; import android.speech.tts.ITextToSpeechCallback; /** @@ -44,11 +45,12 @@ interface ITextToSpeechService { * @param callingInstance a binder representing the identity of the calling * TextToSpeech object. * @param text The text to synthesize. - * @param filename The file to write the synthesized audio to. + * @param fileDescriptor The file descriptor to write the synthesized audio to. Has to be + writable. * @param param Request parameters. */ - int synthesizeToFile(in IBinder callingInstance, in String text, - in String filename, in Bundle params); + int synthesizeToFileDescriptor(in IBinder callingInstance, in String text, + in ParcelFileDescriptor fileDescriptor, in Bundle params); /** * Plays an existing audio resource. diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java index c1af7a5c83165..73d400eb20ac3 100644 --- a/core/java/android/speech/tts/TextToSpeech.java +++ b/core/java/android/speech/tts/TextToSpeech.java @@ -27,11 +27,15 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.IBinder; +import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.provider.Settings; import android.text.TextUtils; import android.util.Log; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -1225,8 +1229,29 @@ public class TextToSpeech { return runAction(new Action() { @Override public Integer run(ITextToSpeechService service) throws RemoteException { - return service.synthesizeToFile(getCallerIdentity(), text, filename, - getParams(params)); + ParcelFileDescriptor fileDescriptor; + int returnValue; + try { + File file = new File(filename); + if(file.exists() && !file.canWrite()) { + Log.e(TAG, "Can't write to " + filename); + return ERROR; + } + fileDescriptor = ParcelFileDescriptor.open(file, + ParcelFileDescriptor.MODE_WRITE_ONLY | + ParcelFileDescriptor.MODE_CREATE | + ParcelFileDescriptor.MODE_TRUNCATE); + returnValue = service.synthesizeToFileDescriptor(getCallerIdentity(), text, + fileDescriptor, getParams(params)); + fileDescriptor.close(); + return returnValue; + } catch (FileNotFoundException e) { + Log.e(TAG, "Opening file " + filename + " failed", e); + return ERROR; + } catch (IOException e) { + Log.e(TAG, "Closing file " + filename + " failed", e); + return ERROR; + } } }, ERROR, "synthesizeToFile"); } diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java index 40547408dc22c..1bcf3e008922a 100644 --- a/core/java/android/speech/tts/TextToSpeechService.java +++ b/core/java/android/speech/tts/TextToSpeechService.java @@ -26,6 +26,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.MessageQueue; +import android.os.ParcelFileDescriptor; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.provider.Settings; @@ -33,7 +34,8 @@ import android.speech.tts.TextToSpeech.Engine; import android.text.TextUtils; import android.util.Log; -import java.io.File; +import java.io.FileDescriptor; +import java.io.FileOutputStream; import java.util.HashMap; import java.util.Locale; import java.util.Set; @@ -654,19 +656,19 @@ public abstract class TextToSpeechService extends Service { } } - private class SynthesisToFileSpeechItem extends SynthesisSpeechItem { - private final File mFile; + private class SynthesisToFileSpeechDescriptorItem extends SynthesisSpeechItem { + private final FileDescriptor mFileDescriptor; - public SynthesisToFileSpeechItem(Object callerIdentity, int callerUid, int callerPid, - Bundle params, String text, - File file) { + public SynthesisToFileSpeechDescriptorItem(Object callerIdentity, int callerUid, + int callerPid, Bundle params, String text, FileDescriptor fileDescriptor) { super(callerIdentity, callerUid, callerPid, params, text); - mFile = file; + mFileDescriptor = fileDescriptor; } @Override protected AbstractSynthesisCallback createSynthesisCallback() { - return new FileSynthesisCallback(mFile); + FileOutputStream fileOutputStream = new FileOutputStream(mFileDescriptor); + return new FileSynthesisCallback(fileOutputStream.getChannel()); } @Override @@ -797,15 +799,15 @@ public abstract class TextToSpeechService extends Service { } @Override - public int synthesizeToFile(IBinder caller, String text, String filename, - Bundle params) { - if (!checkNonNull(caller, text, filename, params)) { + public int synthesizeToFileDescriptor(IBinder caller, String text, ParcelFileDescriptor + fileDescriptor, Bundle params) { + if (!checkNonNull(caller, text, fileDescriptor, params)) { return TextToSpeech.ERROR; } - File file = new File(filename); - SpeechItem item = new SynthesisToFileSpeechItem(caller, Binder.getCallingUid(), - Binder.getCallingPid(), params, text, file); + SpeechItem item = new SynthesisToFileSpeechDescriptorItem(caller, Binder.getCallingUid(), + Binder.getCallingPid(), params, text, + fileDescriptor.getFileDescriptor()); return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item); }