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