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