1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17package com.android.inputmethod.deprecated.voice;
18
19import com.android.common.speech.LoggingEvents;
20import com.android.inputmethod.deprecated.compat.VoiceInputLoggerCompatUtils;
21
22import android.content.Context;
23import android.content.Intent;
24
25/**
26 * Provides the logging facility for voice input events. This fires broadcasts back to
27 * the voice search app which then logs on our behalf.
28 *
29 * Note that debug console logging does not occur in this class. If you want to
30 * see console output of these logging events, there is a boolean switch to turn
31 * on on the VoiceSearch side.
32 */
33public class VoiceInputLogger {
34    @SuppressWarnings("unused")
35    private static final String TAG = VoiceInputLogger.class.getSimpleName();
36
37    private static VoiceInputLogger sVoiceInputLogger;
38
39    private final Context mContext;
40
41    // The base intent used to form all broadcast intents to the logger
42    // in VoiceSearch.
43    private final Intent mBaseIntent;
44
45    // This flag is used to indicate when there are voice events that
46    // need to be flushed.
47    private boolean mHasLoggingInfo = false;
48
49    /**
50     * Returns the singleton of the logger.
51     *
52     * @param contextHint a hint context used when creating the logger instance.
53     * Ignored if the singleton instance already exists.
54     */
55    public static synchronized VoiceInputLogger getLogger(Context contextHint) {
56        if (sVoiceInputLogger == null) {
57            sVoiceInputLogger = new VoiceInputLogger(contextHint);
58        }
59        return sVoiceInputLogger;
60    }
61
62    public VoiceInputLogger(Context context) {
63        mContext = context;
64
65        mBaseIntent = new Intent(LoggingEvents.ACTION_LOG_EVENT);
66        mBaseIntent.putExtra(LoggingEvents.EXTRA_APP_NAME, LoggingEvents.VoiceIme.APP_NAME);
67    }
68
69    private Intent newLoggingBroadcast(int event) {
70        Intent i = new Intent(mBaseIntent);
71        i.putExtra(LoggingEvents.EXTRA_EVENT, event);
72        return i;
73    }
74
75    public void flush() {
76        if (hasLoggingInfo()) {
77            Intent i = new Intent(mBaseIntent);
78            i.putExtra(LoggingEvents.EXTRA_FLUSH, true);
79            mContext.sendBroadcast(i);
80            setHasLoggingInfo(false);
81        }
82    }
83
84    public void keyboardWarningDialogShown() {
85        setHasLoggingInfo(true);
86        mContext.sendBroadcast(newLoggingBroadcast(
87                LoggingEvents.VoiceIme.KEYBOARD_WARNING_DIALOG_SHOWN));
88    }
89
90    public void keyboardWarningDialogDismissed() {
91        setHasLoggingInfo(true);
92        mContext.sendBroadcast(newLoggingBroadcast(
93                LoggingEvents.VoiceIme.KEYBOARD_WARNING_DIALOG_DISMISSED));
94    }
95
96    public void keyboardWarningDialogOk() {
97        setHasLoggingInfo(true);
98        mContext.sendBroadcast(newLoggingBroadcast(
99                LoggingEvents.VoiceIme.KEYBOARD_WARNING_DIALOG_OK));
100    }
101
102    public void keyboardWarningDialogCancel() {
103        setHasLoggingInfo(true);
104        mContext.sendBroadcast(newLoggingBroadcast(
105                LoggingEvents.VoiceIme.KEYBOARD_WARNING_DIALOG_CANCEL));
106    }
107
108    public void settingsWarningDialogShown() {
109        setHasLoggingInfo(true);
110        mContext.sendBroadcast(newLoggingBroadcast(
111                LoggingEvents.VoiceIme.SETTINGS_WARNING_DIALOG_SHOWN));
112    }
113
114    public void settingsWarningDialogDismissed() {
115        setHasLoggingInfo(true);
116        mContext.sendBroadcast(newLoggingBroadcast(
117                LoggingEvents.VoiceIme.SETTINGS_WARNING_DIALOG_DISMISSED));
118    }
119
120    public void settingsWarningDialogOk() {
121        setHasLoggingInfo(true);
122        mContext.sendBroadcast(newLoggingBroadcast(
123                LoggingEvents.VoiceIme.SETTINGS_WARNING_DIALOG_OK));
124    }
125
126    public void settingsWarningDialogCancel() {
127        setHasLoggingInfo(true);
128        mContext.sendBroadcast(newLoggingBroadcast(
129                LoggingEvents.VoiceIme.SETTINGS_WARNING_DIALOG_CANCEL));
130    }
131
132    public void swipeHintDisplayed() {
133        setHasLoggingInfo(true);
134        mContext.sendBroadcast(newLoggingBroadcast(LoggingEvents.VoiceIme.SWIPE_HINT_DISPLAYED));
135    }
136
137    public void cancelDuringListening() {
138        setHasLoggingInfo(true);
139        mContext.sendBroadcast(newLoggingBroadcast(LoggingEvents.VoiceIme.CANCEL_DURING_LISTENING));
140    }
141
142    public void cancelDuringWorking() {
143        setHasLoggingInfo(true);
144        mContext.sendBroadcast(newLoggingBroadcast(LoggingEvents.VoiceIme.CANCEL_DURING_WORKING));
145    }
146
147    public void cancelDuringError() {
148        setHasLoggingInfo(true);
149        mContext.sendBroadcast(newLoggingBroadcast(LoggingEvents.VoiceIme.CANCEL_DURING_ERROR));
150    }
151
152    public void punctuationHintDisplayed() {
153        setHasLoggingInfo(true);
154        mContext.sendBroadcast(newLoggingBroadcast(
155                LoggingEvents.VoiceIme.PUNCTUATION_HINT_DISPLAYED));
156    }
157
158    public void error(int code) {
159        setHasLoggingInfo(true);
160        Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.ERROR);
161        i.putExtra(LoggingEvents.VoiceIme.EXTRA_ERROR_CODE, code);
162        mContext.sendBroadcast(i);
163    }
164
165    public void start(String locale, boolean swipe) {
166        setHasLoggingInfo(true);
167        Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.START);
168        i.putExtra(LoggingEvents.VoiceIme.EXTRA_START_LOCALE, locale);
169        i.putExtra(LoggingEvents.VoiceIme.EXTRA_START_SWIPE, swipe);
170        i.putExtra(LoggingEvents.EXTRA_TIMESTAMP, System.currentTimeMillis());
171        mContext.sendBroadcast(i);
172    }
173
174    public void voiceInputDelivered(int length) {
175        setHasLoggingInfo(true);
176        Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.VOICE_INPUT_DELIVERED);
177        i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_LENGTH, length);
178        mContext.sendBroadcast(i);
179    }
180
181    public void textModifiedByTypingInsertion(int length) {
182        setHasLoggingInfo(true);
183        Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.TEXT_MODIFIED);
184        i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_LENGTH, length);
185        i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_TYPE,
186                LoggingEvents.VoiceIme.TEXT_MODIFIED_TYPE_TYPING_INSERTION);
187        mContext.sendBroadcast(i);
188    }
189
190    public void textModifiedByTypingInsertionPunctuation(int length) {
191        setHasLoggingInfo(true);
192        Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.TEXT_MODIFIED);
193        i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_LENGTH, length);
194        i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_TYPE,
195                LoggingEvents.VoiceIme.TEXT_MODIFIED_TYPE_TYPING_INSERTION_PUNCTUATION);
196        mContext.sendBroadcast(i);
197    }
198
199    public void textModifiedByTypingDeletion(int length) {
200        setHasLoggingInfo(true);
201        Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.TEXT_MODIFIED);
202        i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_LENGTH, length);
203        i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_TYPE,
204                LoggingEvents.VoiceIme.TEXT_MODIFIED_TYPE_TYPING_DELETION);
205
206        mContext.sendBroadcast(i);
207    }
208
209
210    public void textModifiedByChooseSuggestion(int suggestionLength, int replacedPhraseLength,
211                                               int index, String before, String after) {
212        setHasLoggingInfo(true);
213        Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.TEXT_MODIFIED);
214        i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_LENGTH, suggestionLength);
215        i.putExtra(VoiceInputLoggerCompatUtils.EXTRA_TEXT_REPLACED_LENGTH, replacedPhraseLength);
216        i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_TYPE,
217                LoggingEvents.VoiceIme.TEXT_MODIFIED_TYPE_CHOOSE_SUGGESTION);
218        i.putExtra(LoggingEvents.VoiceIme.EXTRA_N_BEST_CHOOSE_INDEX, index);
219        i.putExtra(VoiceInputLoggerCompatUtils.EXTRA_BEFORE_N_BEST_CHOOSE, before);
220        i.putExtra(VoiceInputLoggerCompatUtils.EXTRA_AFTER_N_BEST_CHOOSE, after);
221        mContext.sendBroadcast(i);
222    }
223
224    public void inputEnded() {
225        setHasLoggingInfo(true);
226        mContext.sendBroadcast(newLoggingBroadcast(LoggingEvents.VoiceIme.INPUT_ENDED));
227    }
228
229    public void voiceInputSettingEnabled() {
230        setHasLoggingInfo(true);
231        mContext.sendBroadcast(newLoggingBroadcast(
232                LoggingEvents.VoiceIme.VOICE_INPUT_SETTING_ENABLED));
233    }
234
235    public void voiceInputSettingDisabled() {
236        setHasLoggingInfo(true);
237        mContext.sendBroadcast(newLoggingBroadcast(
238                LoggingEvents.VoiceIme.VOICE_INPUT_SETTING_DISABLED));
239    }
240
241    private void setHasLoggingInfo(boolean hasLoggingInfo) {
242        mHasLoggingInfo = hasLoggingInfo;
243        // If applications that call UserHappinessSignals.userAcceptedImeText
244        // make that call after VoiceInputLogger.flush() calls this method with false, we
245        // will lose those happiness signals. For example, consider the gmail sequence:
246        // 1. compose message
247        // 2. speak message into message field
248        // 3. type subject into subject field
249        // 4. press send
250        // We will NOT get the signal that the user accepted the voice inputted message text
251        // because when the user tapped on the subject field, the ime's flush will be triggered
252        // and the hasLoggingInfo will be then set to false. So by the time the user hits send
253        // we have essentially forgotten about any voice input.
254        // However the following (more common) use case is properly logged
255        // 1. compose message
256        // 2. type subject in subject field
257        // 3. speak message in message field
258        // 4. press send
259        VoiceInputLoggerCompatUtils.setHasVoiceLoggingInfoCompat(hasLoggingInfo);
260    }
261
262    private boolean hasLoggingInfo(){
263        return mHasLoggingInfo;
264    }
265
266}
267