16dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath/*
26dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath * Copyright (C) 2011 The Android Open Source Project
36dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath *
46dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath * Licensed under the Apache License, Version 2.0 (the "License"); you may not
56dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath * use this file except in compliance with the License. You may obtain a copy of
66dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath * the License at
76dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath *
86dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath * http://www.apache.org/licenses/LICENSE-2.0
96dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath *
106dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath * Unless required by applicable law or agreed to in writing, software
116dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
126dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
136dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath * License for the specific language governing permissions and limitations under
146dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath * the License.
156dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath */
166dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamathpackage android.speech.tts;
176dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath
186dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamathimport android.os.SystemClock;
196dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamathimport android.text.TextUtils;
2067ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamathimport android.util.Log;
216dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath
226dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath/**
236dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath * Writes data about a given speech synthesis request to the event logs.
246dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath * The data that is logged includes the calling app, length of the utterance,
256dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath * speech rate / pitch and the latency and overall time taken.
266dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath *
276dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath * Note that {@link EventLogger#onStopped()} and {@link EventLogger#onError()}
2867ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath * might be called from any thread, but on {@link EventLogger#onAudioDataWritten()} and
296dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath * {@link EventLogger#onComplete()} must be called from a single thread
306dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath * (usually the audio playback thread}
316dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath */
326dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamathclass EventLogger {
336dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    private final SynthesisRequest mRequest;
346dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    private final String mServiceApp;
35492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath    private final int mCallerUid;
36492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath    private final int mCallerPid;
376dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    private final long mReceivedTime;
386dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    private long mPlaybackStartTime = -1;
396dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    private volatile long mRequestProcessingStartTime = -1;
406dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    private volatile long mEngineStartTime = -1;
416dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    private volatile long mEngineCompleteTime = -1;
426dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath
436dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    private volatile boolean mError = false;
446dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    private volatile boolean mStopped = false;
456dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    private boolean mLogWritten = false;
466dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath
47492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath    EventLogger(SynthesisRequest request, int callerUid, int callerPid, String serviceApp) {
486dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        mRequest = request;
49492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        mCallerUid = callerUid;
50492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        mCallerPid = callerPid;
516dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        mServiceApp = serviceApp;
526dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        mReceivedTime = SystemClock.elapsedRealtime();
536dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    }
546dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath
556dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    /**
566dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath     * Notifies the logger that this request has been selected from
576dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath     * the processing queue for processing. Engine latency / total time
586dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath     * is measured from this baseline.
596dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath     */
606dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    public void onRequestProcessingStart() {
616dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        mRequestProcessingStartTime = SystemClock.elapsedRealtime();
626dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    }
636dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath
646dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    /**
656dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath     * Notifies the logger that a chunk of data has been received from
666dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath     * the engine. Might be called multiple times.
676dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath     */
686dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    public void onEngineDataReceived() {
696dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        if (mEngineStartTime == -1) {
706dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath            mEngineStartTime = SystemClock.elapsedRealtime();
716dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        }
726dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    }
736dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath
746dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    /**
756dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath     * Notifies the logger that the engine has finished processing data.
766dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath     * Will be called exactly once.
776dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath     */
786dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    public void onEngineComplete() {
796dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        mEngineCompleteTime = SystemClock.elapsedRealtime();
806dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    }
816dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath
826dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    /**
836dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath     * Notifies the logger that audio playback has started for some section
846dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath     * of the synthesis. This is normally some amount of time after the engine
8567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath     * has synthesized data and varies depending on utterances and
866dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath     * other audio currently in the queue.
876dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath     */
8867ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    public void onAudioDataWritten() {
896dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        // For now, keep track of only the first chunk of audio
906dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        // that was played.
916dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        if (mPlaybackStartTime == -1) {
926dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath            mPlaybackStartTime = SystemClock.elapsedRealtime();
936dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        }
946dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    }
956dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath
966dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    /**
976dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath     * Notifies the logger that the current synthesis was stopped.
986dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath     * Latency numbers are not reported for stopped syntheses.
996dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath     */
1006dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    public void onStopped() {
1016dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        mStopped = false;
1026dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    }
1036dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath
1046dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    /**
1056dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath     * Notifies the logger that the current synthesis resulted in
1066dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath     * an error. This is logged using {@link EventLogTags#writeTtsSpeakFailure}.
1076dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath     */
1086dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    public void onError() {
1096dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        mError = true;
1106dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    }
1116dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath
1126dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    /**
1136dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath     * Notifies the logger that the current synthesis has completed.
1146dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath     * All available data is not logged.
1156dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath     */
1166dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    public void onWriteData() {
1176dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        if (mLogWritten) {
1186dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath            return;
1196dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        } else {
1206dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath            mLogWritten = true;
1216dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        }
1226dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath
1236dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        long completionTime = SystemClock.elapsedRealtime();
12467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        // onAudioDataWritten() should normally always be called if an
1256dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        // error does not occur.
1266dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        if (mError || mPlaybackStartTime == -1 || mEngineCompleteTime == -1) {
127492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath            EventLogTags.writeTtsSpeakFailure(mServiceApp, mCallerUid, mCallerPid,
1286dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath                    getUtteranceLength(), getLocaleString(),
1296dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath                    mRequest.getSpeechRate(), mRequest.getPitch());
1306dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath            return;
1316dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        }
1326dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath
1336dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        // We don't report stopped syntheses because their overall
1346dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        // total time spent will be innacurate (will not correlate with
1356dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        // the length of the utterance).
1366dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        if (mStopped) {
1376dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath            return;
1386dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        }
1396dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath
1406dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        final long audioLatency = mPlaybackStartTime - mReceivedTime;
1416dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        final long engineLatency = mEngineStartTime - mRequestProcessingStartTime;
1426dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        final long engineTotal = mEngineCompleteTime - mRequestProcessingStartTime;
14367ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
144492b7f0d51f53164aa6eb974cd7ab6a7889af677Narayan Kamath        EventLogTags.writeTtsSpeakSuccess(mServiceApp, mCallerUid, mCallerPid,
1456dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath                getUtteranceLength(), getLocaleString(),
1466dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath                mRequest.getSpeechRate(), mRequest.getPitch(),
1476dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath                engineLatency, engineTotal, audioLatency);
1486dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    }
1496dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath
1506dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    /**
1516dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath     * @return the length of the utterance for the given synthesis, 0
1526dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath     *          if the utterance was {@code null}.
1536dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath     */
1546dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    private int getUtteranceLength() {
1556dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        final String utterance = mRequest.getText();
1566dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        return utterance == null ? 0 : utterance.length();
1576dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    }
1586dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath
1596dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    /**
1606dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath     * Returns a formatted locale string from the synthesis params of the
1616dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath     * form lang-country-variant.
1626dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath     */
1636dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    private String getLocaleString() {
1646dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        StringBuilder sb = new StringBuilder(mRequest.getLanguage());
1656dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        if (!TextUtils.isEmpty(mRequest.getCountry())) {
1666dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath            sb.append('-');
1676dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath            sb.append(mRequest.getCountry());
1686dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath
1696dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath            if (!TextUtils.isEmpty(mRequest.getVariant())) {
1706dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath                sb.append('-');
1716dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath                sb.append(mRequest.getVariant());
1726dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath            }
1736dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        }
1746dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath
1756dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath        return sb.toString();
1766dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath    }
1776dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath
1786dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath}
179