/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.android.inputmethod.latin; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.inputmethodservice.InputMethodService; import android.os.Build; import android.os.Handler; import android.os.HandlerThread; import android.os.Process; import android.os.SystemClock; import android.preference.PreferenceManager; import android.text.TextUtils; import android.util.Log; import android.view.MotionEvent; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.KeyDetector; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.internal.KeyboardState; import com.android.inputmethod.latin.EditingUtils.Range; import com.android.inputmethod.latin.define.ProductionFlag; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.channels.FileChannel; import java.nio.charset.Charset; import java.util.Map; import java.util.UUID; /** * Logs the use of the LatinIME keyboard. * * This class logs operations on the IME keyboard, including what the user has typed. * Data is stored locally in a file in app-specific storage. * * This functionality is off by default. See {@link ProductionFlag.IS_EXPERIMENTAL}. */ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChangeListener { private static final String TAG = ResearchLogger.class.getSimpleName(); private static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode"; private static final String PREF_RESEARCH_LOGGER_UUID_STRING = "pref_research_logger_uuid"; private static final boolean DEBUG = false; private static final String WHITESPACE_SEPARATORS = " \t\n\r"; private static final ResearchLogger sInstance = new ResearchLogger(new LogFileManager()); private static final int MAX_INPUTVIEW_LENGTH_TO_CAPTURE = 8192; // must be >=1 public static boolean sIsLogging = false; /* package */ final Handler mLoggingHandler; private InputMethodService mIms; // set when LatinIME should ignore a onUpdateSelection() callback that // arises from operations in this class private static boolean mLatinIMEExpectingUpdateSelection = false; /** * Isolates management of files. This variable should never be null, but can be changed * to support testing. */ /* package */ LogFileManager mLogFileManager; /** * Manages the file(s) that stores the logs. * * Handles creation, deletion, and provides Readers, Writers, and InputStreams to access * the logs. */ /* package */ static class LogFileManager { public static final String RESEARCH_LOG_FILENAME_KEY = "RESEARCH_LOG_FILENAME"; private static final String DEFAULT_FILENAME = "researchLog.txt"; private static final long LOGFILE_PURGE_INTERVAL = 1000 * 60 * 60 * 24; protected InputMethodService mIms; protected File mFile; protected PrintWriter mPrintWriter; /* package */ LogFileManager() { } public void init(final InputMethodService ims) { mIms = ims; } public synchronized void createLogFile() throws IOException { createLogFile(DEFAULT_FILENAME); } public synchronized void createLogFile(final SharedPreferences prefs) throws IOException { final String filename = prefs.getString(RESEARCH_LOG_FILENAME_KEY, DEFAULT_FILENAME); createLogFile(filename); } public synchronized void createLogFile(final String filename) throws IOException { if (mIms == null) { final String msg = "InputMethodService is not configured. Logging is off."; Log.w(TAG, msg); throw new IOException(msg); } final File filesDir = mIms.getFilesDir(); if (filesDir == null || !filesDir.exists()) { final String msg = "Storage directory does not exist. Logging is off."; Log.w(TAG, msg); throw new IOException(msg); } close(); final File file = new File(filesDir, filename); mFile = file; boolean append = true; if (file.exists() && file.lastModified() + LOGFILE_PURGE_INTERVAL < System.currentTimeMillis()) { append = false; } mPrintWriter = new PrintWriter(new BufferedWriter(new FileWriter(file, append)), true); } public synchronized boolean append(final String s) { PrintWriter printWriter = mPrintWriter; if (printWriter == null || !mFile.exists()) { if (DEBUG) { Log.w(TAG, "PrintWriter is null... attempting to create default log file"); } try { createLogFile(); printWriter = mPrintWriter; } catch (IOException e) { Log.w(TAG, "Failed to create log file. Not logging."); return false; } } printWriter.print(s); printWriter.flush(); return !printWriter.checkError(); } public synchronized void reset() { if (mPrintWriter != null) { mPrintWriter.close(); mPrintWriter = null; if (DEBUG) { Log.d(TAG, "logfile closed"); } } if (mFile != null) { mFile.delete(); if (DEBUG) { Log.d(TAG, "logfile deleted"); } mFile = null; } } public synchronized void close() { if (mPrintWriter != null) { mPrintWriter.close(); mPrintWriter = null; mFile = null; if (DEBUG) { Log.d(TAG, "logfile closed"); } } } /* package */ synchronized void flush() { if (mPrintWriter != null) { mPrintWriter.flush(); } } /* package */ synchronized String getContents() { final File file = mFile; if (file == null) { return ""; } if (mPrintWriter != null) { mPrintWriter.flush(); } FileInputStream stream = null; FileChannel fileChannel = null; String s = ""; try { stream = new FileInputStream(file); fileChannel = stream.getChannel(); final ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length()); fileChannel.read(byteBuffer); byteBuffer.rewind(); CharBuffer charBuffer = Charset.defaultCharset().decode(byteBuffer); s = charBuffer.toString(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (fileChannel != null) { fileChannel.close(); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (stream != null) { stream.close(); } } catch (IOException e) { e.printStackTrace(); } } } return s; } } private ResearchLogger(final LogFileManager logFileManager) { final HandlerThread handlerThread = new HandlerThread("ResearchLogger logging task", Process.THREAD_PRIORITY_BACKGROUND); handlerThread.start(); mLoggingHandler = new Handler(handlerThread.getLooper()); mLogFileManager = logFileManager; } public static ResearchLogger getInstance() { return sInstance; } public static void init(final InputMethodService ims, final SharedPreferences prefs) { sInstance.initInternal(ims, prefs); } /* package */ void initInternal(final InputMethodService ims, final SharedPreferences prefs) { mIms = ims; final LogFileManager logFileManager = mLogFileManager; if (logFileManager != null) { logFileManager.init(ims); try { logFileManager.createLogFile(prefs); } catch (IOException e) { e.printStackTrace(); } } if (prefs != null) { sIsLogging = prefs.getBoolean(PREF_USABILITY_STUDY_MODE, false); prefs.registerOnSharedPreferenceChangeListener(this); } } /** * Represents a category of logging events that share the same subfield structure. */ private static enum LogGroup { MOTION_EVENT("m"), KEY("k"), CORRECTION("c"), STATE_CHANGE("s"), UNSTRUCTURED("u"); private final String mLogString; private LogGroup(final String logString) { mLogString = logString; } } public void logMotionEvent(final int action, final long eventTime, final int id, final int x, final int y, final float size, final float pressure) { final String eventTag; switch (action) { case MotionEvent.ACTION_CANCEL: eventTag = "[Cancel]"; break; case MotionEvent.ACTION_UP: eventTag = "[Up]"; break; case MotionEvent.ACTION_DOWN: eventTag = "[Down]"; break; case MotionEvent.ACTION_POINTER_UP: eventTag = "[PointerUp]"; break; case MotionEvent.ACTION_POINTER_DOWN: eventTag = "[PointerDown]"; break; case MotionEvent.ACTION_MOVE: eventTag = "[Move]"; break; case MotionEvent.ACTION_OUTSIDE: eventTag = "[Outside]"; break; default: eventTag = "[Action" + action + "]"; break; } if (!TextUtils.isEmpty(eventTag)) { final StringBuilder sb = new StringBuilder(); sb.append(eventTag); sb.append('\t'); sb.append(eventTime); sb.append('\t'); sb.append(id); sb.append('\t'); sb.append(x); sb.append('\t'); sb.append(y); sb.append('\t'); sb.append(size); sb.append('\t'); sb.append(pressure); write(LogGroup.MOTION_EVENT, sb.toString()); } } public void logKeyEvent(final int code, final int x, final int y) { final StringBuilder sb = new StringBuilder(); sb.append(Keyboard.printableCode(code)); sb.append('\t'); sb.append(x); sb.append('\t'); sb.append(y); write(LogGroup.KEY, sb.toString()); } public void logCorrection(final String subgroup, final String before, final String after, final int position) { final StringBuilder sb = new StringBuilder(); sb.append(subgroup); sb.append('\t'); sb.append(before); sb.append('\t'); sb.append(after); sb.append('\t'); sb.append(position); write(LogGroup.CORRECTION, sb.toString()); } public void logStateChange(final String subgroup, final String details) { write(LogGroup.STATE_CHANGE, subgroup + "\t" + details); } public static class UnsLogGroup { private static final boolean DEFAULT_ENABLED = true; private static final boolean KEYBOARDSTATE_ONCANCELINPUT_ENABLED = DEFAULT_ENABLED; private static final boolean KEYBOARDSTATE_ONCODEINPUT_ENABLED = DEFAULT_ENABLED; private static final boolean KEYBOARDSTATE_ONLONGPRESSTIMEOUT_ENABLED = DEFAULT_ENABLED; private static final boolean KEYBOARDSTATE_ONPRESSKEY_ENABLED = DEFAULT_ENABLED; private static final boolean KEYBOARDSTATE_ONRELEASEKEY_ENABLED = DEFAULT_ENABLED; private static final boolean LATINIME_COMMITCURRENTAUTOCORRECTION_ENABLED = DEFAULT_ENABLED; private static final boolean LATINIME_COMMITTEXT_ENABLED = DEFAULT_ENABLED; private static final boolean LATINIME_DELETESURROUNDINGTEXT_ENABLED = DEFAULT_ENABLED; private static final boolean LATINIME_DOUBLESPACEAUTOPERIOD_ENABLED = DEFAULT_ENABLED; private static final boolean LATINIME_ONDISPLAYCOMPLETIONS_ENABLED = DEFAULT_ENABLED; private static final boolean LATINIME_ONWINDOWHIDDEN_ENABLED = DEFAULT_ENABLED; private static final boolean LATINIME_ONSTARTINPUTVIEWINTERNAL_ENABLED = DEFAULT_ENABLED; private static final boolean LATINIME_ONUPDATESELECTION_ENABLED = DEFAULT_ENABLED; private static final boolean LATINIME_PERFORMEDITORACTION_ENABLED = DEFAULT_ENABLED; private static final boolean LATINIME_PICKAPPLICATIONSPECIFIEDCOMPLETION_ENABLED = DEFAULT_ENABLED; private static final boolean LATINIME_PICKPUNCTUATIONSUGGESTION_ENABLED = DEFAULT_ENABLED; private static final boolean LATINIME_PICKSUGGESTIONMANUALLY_ENABLED = DEFAULT_ENABLED; private static final boolean LATINIME_REVERTCOMMIT_ENABLED = DEFAULT_ENABLED; private static final boolean LATINIME_REVERTDOUBLESPACEWHILEINBATCHEDIT_ENABLED = DEFAULT_ENABLED; private static final boolean LATINIME_REVERTSWAPPUNCTUATION_ENABLED = DEFAULT_ENABLED; private static final boolean LATINIME_SENDKEYCODEPOINT_ENABLED = DEFAULT_ENABLED; private static final boolean LATINIME_SWAPSWAPPERANDSPACEWHILEINBATCHEDIT_ENABLED = DEFAULT_ENABLED; private static final boolean LATINIME_SWITCHTOKEYBOARDVIEW_ENABLED = DEFAULT_ENABLED; private static final boolean LATINKEYBOARDVIEW_ONLONGPRESS_ENABLED = DEFAULT_ENABLED; private static final boolean LATINKEYBOARDVIEW_ONPROCESSMOTIONEVENT_ENABLED = DEFAULT_ENABLED; private static final boolean LATINKEYBOARDVIEW_SETKEYBOARD_ENABLED = DEFAULT_ENABLED; private static final boolean POINTERTRACKER_CALLLISTENERONCANCELINPUT_ENABLED = DEFAULT_ENABLED; private static final boolean POINTERTRACKER_CALLLISTENERONCODEINPUT_ENABLED = DEFAULT_ENABLED; private static final boolean POINTERTRACKER_CALLLISTENERONPRESSANDCHECKKEYBOARDLAYOUTCHANGE_ENABLED = DEFAULT_ENABLED; private static final boolean POINTERTRACKER_CALLLISTENERONRELEASE_ENABLED = DEFAULT_ENABLED; private static final boolean POINTERTRACKER_ONDOWNEVENT_ENABLED = DEFAULT_ENABLED; private static final boolean POINTERTRACKER_ONMOVEEVENT_ENABLED = DEFAULT_ENABLED; private static final boolean SUDDENJUMPINGTOUCHEVENTHANDLER_ONTOUCHEVENT_ENABLED = DEFAULT_ENABLED; private static final boolean SUGGESTIONSVIEW_SETSUGGESTIONS_ENABLED = DEFAULT_ENABLED; } public static void logUnstructured(String logGroup, final String details) { // TODO: improve performance by making entire class static and/or implementing natively getInstance().write(LogGroup.UNSTRUCTURED, logGroup + "\t" + details); } private void write(final LogGroup logGroup, final String log) { // TODO: rewrite in native for better performance mLoggingHandler.post(new Runnable() { @Override public void run() { final long currentTime = System.currentTimeMillis(); final long upTime = SystemClock.uptimeMillis(); final StringBuilder builder = new StringBuilder(); builder.append(currentTime); builder.append('\t'); builder.append(upTime); builder.append('\t'); builder.append(logGroup.mLogString); builder.append('\t'); builder.append(log); builder.append('\n'); if (DEBUG) { Log.d(TAG, "Write: " + '[' + logGroup.mLogString + ']' + log); } final String s = builder.toString(); if (mLogFileManager.append(s)) { // success } else { if (DEBUG) { Log.w(TAG, "Unable to write to log."); } // perhaps logfile was deleted. try to recreate and relog. try { mLogFileManager.createLogFile(PreferenceManager .getDefaultSharedPreferences(mIms)); mLogFileManager.append(s); } catch (IOException e) { e.printStackTrace(); } } } }); } public void clearAll() { mLoggingHandler.post(new Runnable() { @Override public void run() { if (DEBUG) { Log.d(TAG, "Delete log file."); } mLogFileManager.reset(); } }); } /* package */ LogFileManager getLogFileManager() { return mLogFileManager; } @Override public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { if (key == null || prefs == null) { return; } sIsLogging = prefs.getBoolean(PREF_USABILITY_STUDY_MODE, false); } public static void keyboardState_onCancelInput(final boolean isSinglePointer, final KeyboardState keyboardState) { if (UnsLogGroup.KEYBOARDSTATE_ONCANCELINPUT_ENABLED) { final String s = "onCancelInput: single=" + isSinglePointer + " " + keyboardState; logUnstructured("KeyboardState_onCancelInput", s); } } public static void keyboardState_onCodeInput( final int code, final boolean isSinglePointer, final boolean autoCaps, final KeyboardState keyboardState) { if (UnsLogGroup.KEYBOARDSTATE_ONCODEINPUT_ENABLED) { final String s = "onCodeInput: code=" + Keyboard.printableCode(code) + " single=" + isSinglePointer + " autoCaps=" + autoCaps + " " + keyboardState; logUnstructured("KeyboardState_onCodeInput", s); } } public static void keyboardState_onLongPressTimeout(final int code, final KeyboardState keyboardState) { if (UnsLogGroup.KEYBOARDSTATE_ONLONGPRESSTIMEOUT_ENABLED) { final String s = "onLongPressTimeout: code=" + Keyboard.printableCode(code) + " " + keyboardState; logUnstructured("KeyboardState_onLongPressTimeout", s); } } public static void keyboardState_onPressKey(final int code, final KeyboardState keyboardState) { if (UnsLogGroup.KEYBOARDSTATE_ONPRESSKEY_ENABLED) { final String s = "onPressKey: code=" + Keyboard.printableCode(code) + " " + keyboardState; logUnstructured("KeyboardState_onPressKey", s); } } public static void keyboardState_onReleaseKey(final KeyboardState keyboardState, final int code, final boolean withSliding) { if (UnsLogGroup.KEYBOARDSTATE_ONRELEASEKEY_ENABLED) { final String s = "onReleaseKey: code=" + Keyboard.printableCode(code) + " sliding=" + withSliding + " " + keyboardState; logUnstructured("KeyboardState_onReleaseKey", s); } } public static void latinIME_commitCurrentAutoCorrection(final String typedWord, final String autoCorrection) { if (UnsLogGroup.LATINIME_COMMITCURRENTAUTOCORRECTION_ENABLED) { if (typedWord.equals(autoCorrection)) { getInstance().logCorrection("[----]", typedWord, autoCorrection, -1); } else { getInstance().logCorrection("[Auto]", typedWord, autoCorrection, -1); } } } public static void latinIME_commitText(final CharSequence typedWord) { if (UnsLogGroup.LATINIME_COMMITTEXT_ENABLED) { logUnstructured("LatinIME_commitText", typedWord.toString()); } } public static void latinIME_deleteSurroundingText(final int length) { if (UnsLogGroup.LATINIME_DELETESURROUNDINGTEXT_ENABLED) { logUnstructured("LatinIME_deleteSurroundingText", String.valueOf(length)); } } public static void latinIME_doubleSpaceAutoPeriod() { if (UnsLogGroup.LATINIME_DOUBLESPACEAUTOPERIOD_ENABLED) { logUnstructured("LatinIME_doubleSpaceAutoPeriod", ""); } } public static void latinIME_onDisplayCompletions( final CompletionInfo[] applicationSpecifiedCompletions) { if (UnsLogGroup.LATINIME_ONDISPLAYCOMPLETIONS_ENABLED) { final StringBuilder builder = new StringBuilder(); builder.append("Received completions:"); if (applicationSpecifiedCompletions != null) { for (int i = 0; i < applicationSpecifiedCompletions.length; i++) { builder.append(" #"); builder.append(i); builder.append(": "); builder.append(applicationSpecifiedCompletions[i]); builder.append("\n"); } } logUnstructured("LatinIME_onDisplayCompletions", builder.toString()); } } /* package */ static boolean getAndClearLatinIMEExpectingUpdateSelection() { boolean returnValue = mLatinIMEExpectingUpdateSelection; mLatinIMEExpectingUpdateSelection = false; return returnValue; } public static void latinIME_onWindowHidden(final int savedSelectionStart, final int savedSelectionEnd, final InputConnection ic) { if (UnsLogGroup.LATINIME_ONWINDOWHIDDEN_ENABLED) { if (ic != null) { ic.beginBatchEdit(); ic.performContextMenuAction(android.R.id.selectAll); CharSequence charSequence = ic.getSelectedText(0); ic.setSelection(savedSelectionStart, savedSelectionEnd); ic.endBatchEdit(); mLatinIMEExpectingUpdateSelection = true; if (TextUtils.isEmpty(charSequence)) { logUnstructured("LatinIME_onWindowHidden", ""); } else { if (charSequence.length() > MAX_INPUTVIEW_LENGTH_TO_CAPTURE) { int length = MAX_INPUTVIEW_LENGTH_TO_CAPTURE; // do not cut in the middle of a supplementary character final char c = charSequence.charAt(length-1); if (Character.isHighSurrogate(c)) { length--; } final CharSequence truncatedCharSequence = charSequence.subSequence(0, length); logUnstructured("LatinIME_onWindowHidden", truncatedCharSequence.toString() + ""); } else { logUnstructured("LatinIME_onWindowHidden", charSequence.toString()); } } } } } public static void latinIME_onStartInputViewInternal(final EditorInfo editorInfo, final SharedPreferences prefs) { if (UnsLogGroup.LATINIME_ONSTARTINPUTVIEWINTERNAL_ENABLED) { final StringBuilder builder = new StringBuilder(); builder.append("onStartInputView: editorInfo:"); builder.append("\tpackageName="); builder.append(editorInfo.packageName); builder.append("\tinputType="); builder.append(Integer.toHexString(editorInfo.inputType)); builder.append("\timeOptions="); builder.append(Integer.toHexString(editorInfo.imeOptions)); builder.append("\tdisplay="); builder.append(Build.DISPLAY); builder.append("\tmodel="); builder.append(Build.MODEL); for (Map.Entry entry : prefs.getAll().entrySet()) { builder.append("\t" + entry.getKey()); Object value = entry.getValue(); builder.append("=" + ((value == null) ? "" : value.toString())); } builder.append("\tuuid="); builder.append(getUUID(prefs)); logUnstructured("LatinIME_onStartInputViewInternal", builder.toString()); } } private static String getUUID(final SharedPreferences prefs) { String uuidString = prefs.getString(PREF_RESEARCH_LOGGER_UUID_STRING, null); if (null == uuidString) { UUID uuid = UUID.randomUUID(); uuidString = uuid.toString(); Editor editor = prefs.edit(); editor.putString(PREF_RESEARCH_LOGGER_UUID_STRING, uuidString); editor.apply(); } return uuidString; } public static void latinIME_onUpdateSelection(final int lastSelectionStart, final int lastSelectionEnd, final int oldSelStart, final int oldSelEnd, final int newSelStart, final int newSelEnd, final int composingSpanStart, final int composingSpanEnd, final boolean expectingUpdateSelection, final boolean expectingUpdateSelectionFromLogger, final InputConnection connection) { if (UnsLogGroup.LATINIME_ONUPDATESELECTION_ENABLED) { final String s = "onUpdateSelection: oss=" + oldSelStart + ", ose=" + oldSelEnd + ", lss=" + lastSelectionStart + ", lse=" + lastSelectionEnd + ", nss=" + newSelStart + ", nse=" + newSelEnd + ", cs=" + composingSpanStart + ", ce=" + composingSpanEnd + ", eus=" + expectingUpdateSelection + ", eusfl=" + expectingUpdateSelectionFromLogger + ", context=\"" + EditingUtils.getWordRangeAtCursor(connection, WHITESPACE_SEPARATORS, 1).mWord + "\""; logUnstructured("LatinIME_onUpdateSelection", s); } } public static void latinIME_performEditorAction(final int imeActionNext) { if (UnsLogGroup.LATINIME_PERFORMEDITORACTION_ENABLED) { logUnstructured("LatinIME_performEditorAction", String.valueOf(imeActionNext)); } } public static void latinIME_pickApplicationSpecifiedCompletion(final int index, final CharSequence text, int x, int y) { if (UnsLogGroup.LATINIME_PICKAPPLICATIONSPECIFIEDCOMPLETION_ENABLED) { final String s = String.valueOf(index) + '\t' + text + '\t' + x + '\t' + y; logUnstructured("LatinIME_pickApplicationSpecifiedCompletion", s); } } public static void latinIME_pickSuggestionManually(final String replacedWord, final int index, CharSequence suggestion, int x, int y) { if (UnsLogGroup.LATINIME_PICKSUGGESTIONMANUALLY_ENABLED) { final String s = String.valueOf(index) + '\t' + suggestion + '\t' + x + '\t' + y; logUnstructured("LatinIME_pickSuggestionManually", s); } } public static void latinIME_punctuationSuggestion(final int index, final CharSequence suggestion, int x, int y) { if (UnsLogGroup.LATINIME_PICKPUNCTUATIONSUGGESTION_ENABLED) { final String s = String.valueOf(index) + '\t' + suggestion + '\t' + x + '\t' + y; logUnstructured("LatinIME_pickPunctuationSuggestion", s); } } public static void latinIME_revertDoubleSpaceWhileInBatchEdit() { if (UnsLogGroup.LATINIME_REVERTDOUBLESPACEWHILEINBATCHEDIT_ENABLED) { logUnstructured("LatinIME_revertDoubleSpaceWhileInBatchEdit", ""); } } public static void latinIME_revertSwapPunctuation() { if (UnsLogGroup.LATINIME_REVERTSWAPPUNCTUATION_ENABLED) { logUnstructured("LatinIME_revertSwapPunctuation", ""); } } public static void latinIME_sendKeyCodePoint(final int code) { if (UnsLogGroup.LATINIME_SENDKEYCODEPOINT_ENABLED) { logUnstructured("LatinIME_sendKeyCodePoint", String.valueOf(code)); } } public static void latinIME_swapSwapperAndSpaceWhileInBatchEdit() { if (UnsLogGroup.LATINIME_SWAPSWAPPERANDSPACEWHILEINBATCHEDIT_ENABLED) { logUnstructured("latinIME_swapSwapperAndSpaceWhileInBatchEdit", ""); } } public static void latinIME_switchToKeyboardView() { if (UnsLogGroup.LATINIME_SWITCHTOKEYBOARDVIEW_ENABLED) { final String s = "Switch to keyboard view."; logUnstructured("LatinIME_switchToKeyboardView", s); } } public static void latinKeyboardView_onLongPress() { if (UnsLogGroup.LATINKEYBOARDVIEW_ONLONGPRESS_ENABLED) { final String s = "long press detected"; logUnstructured("LatinKeyboardView_onLongPress", s); } } public static void latinKeyboardView_processMotionEvent(MotionEvent me, int action, long eventTime, int index, int id, int x, int y) { if (UnsLogGroup.LATINKEYBOARDVIEW_ONPROCESSMOTIONEVENT_ENABLED) { final float size = me.getSize(index); final float pressure = me.getPressure(index); if (action != MotionEvent.ACTION_MOVE) { getInstance().logMotionEvent(action, eventTime, id, x, y, size, pressure); } } } public static void latinKeyboardView_setKeyboard(final Keyboard keyboard) { if (UnsLogGroup.LATINKEYBOARDVIEW_SETKEYBOARD_ENABLED) { StringBuilder builder = new StringBuilder(); builder.append("id="); builder.append(keyboard.mId); builder.append("\tw="); builder.append(keyboard.mOccupiedWidth); builder.append("\th="); builder.append(keyboard.mOccupiedHeight); builder.append("\tkeys=["); boolean first = true; for (Key key : keyboard.mKeys) { if (first) { first = false; } else { builder.append(","); } builder.append("{code:"); builder.append(key.mCode); builder.append(",altCode:"); builder.append(key.mAltCode); builder.append(",x:"); builder.append(key.mX); builder.append(",y:"); builder.append(key.mY); builder.append(",w:"); builder.append(key.mWidth); builder.append(",h:"); builder.append(key.mHeight); builder.append("}"); } builder.append("]"); logUnstructured("LatinKeyboardView_setKeyboard", builder.toString()); } } public static void latinIME_revertCommit(final String originallyTypedWord) { if (UnsLogGroup.LATINIME_REVERTCOMMIT_ENABLED) { logUnstructured("LatinIME_revertCommit", originallyTypedWord); } } public static void pointerTracker_callListenerOnCancelInput() { final String s = "onCancelInput"; if (UnsLogGroup.POINTERTRACKER_CALLLISTENERONCANCELINPUT_ENABLED) { logUnstructured("PointerTracker_callListenerOnCancelInput", s); } } public static void pointerTracker_callListenerOnCodeInput(final Key key, final int x, final int y, final boolean ignoreModifierKey, final boolean altersCode, final int code) { if (UnsLogGroup.POINTERTRACKER_CALLLISTENERONCODEINPUT_ENABLED) { final String s = "onCodeInput: " + Keyboard.printableCode(code) + " text=" + key.mOutputText + " x=" + x + " y=" + y + " ignoreModifier=" + ignoreModifierKey + " altersCode=" + altersCode + " enabled=" + key.isEnabled(); logUnstructured("PointerTracker_callListenerOnCodeInput", s); } } public static void pointerTracker_callListenerOnPressAndCheckKeyboardLayoutChange( final Key key, final boolean ignoreModifierKey) { if (UnsLogGroup.POINTERTRACKER_CALLLISTENERONPRESSANDCHECKKEYBOARDLAYOUTCHANGE_ENABLED) { final String s = "onPress : " + KeyDetector.printableCode(key) + " ignoreModifier=" + ignoreModifierKey + " enabled=" + key.isEnabled(); logUnstructured("PointerTracker_callListenerOnPressAndCheckKeyboardLayoutChange", s); } } public static void pointerTracker_callListenerOnRelease(final Key key, final int primaryCode, final boolean withSliding, final boolean ignoreModifierKey) { if (UnsLogGroup.POINTERTRACKER_CALLLISTENERONRELEASE_ENABLED) { final String s = "onRelease : " + Keyboard.printableCode(primaryCode) + " sliding=" + withSliding + " ignoreModifier=" + ignoreModifierKey + " enabled="+ key.isEnabled(); logUnstructured("PointerTracker_callListenerOnRelease", s); } } public static void pointerTracker_onDownEvent(long deltaT, int distanceSquared) { if (UnsLogGroup.POINTERTRACKER_ONDOWNEVENT_ENABLED) { final String s = "onDownEvent: ignore potential noise: time=" + deltaT + " distance=" + distanceSquared; logUnstructured("PointerTracker_onDownEvent", s); } } public static void pointerTracker_onMoveEvent(final int x, final int y, final int lastX, final int lastY) { if (UnsLogGroup.POINTERTRACKER_ONMOVEEVENT_ENABLED) { final String s = String.format("onMoveEvent: sudden move is translated to " + "up[%d,%d]/down[%d,%d] events", lastX, lastY, x, y); logUnstructured("PointerTracker_onMoveEvent", s); } } public static void suddenJumpingTouchEventHandler_onTouchEvent(final MotionEvent me) { if (UnsLogGroup.SUDDENJUMPINGTOUCHEVENTHANDLER_ONTOUCHEVENT_ENABLED) { final String s = "onTouchEvent: ignore sudden jump " + me; logUnstructured("SuddenJumpingTouchEventHandler_onTouchEvent", s); } } public static void suggestionsView_setSuggestions(final SuggestedWords mSuggestedWords) { if (UnsLogGroup.SUGGESTIONSVIEW_SETSUGGESTIONS_ENABLED) { logUnstructured("SuggestionsView_setSuggestions", mSuggestedWords.toString()); } } }