Home | History | Annotate | Download | only in latin
      1 /*
      2  * Copyright (C) 2012 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 
     17 package com.android.inputmethod.latin;
     18 
     19 import android.content.SharedPreferences;
     20 import android.inputmethodservice.InputMethodService;
     21 import android.os.Build;
     22 import android.os.Handler;
     23 import android.os.HandlerThread;
     24 import android.os.Process;
     25 import android.os.SystemClock;
     26 import android.preference.PreferenceManager;
     27 import android.text.TextUtils;
     28 import android.util.Log;
     29 import android.view.MotionEvent;
     30 import android.view.inputmethod.CompletionInfo;
     31 import android.view.inputmethod.EditorInfo;
     32 
     33 import com.android.inputmethod.keyboard.Key;
     34 import com.android.inputmethod.keyboard.KeyDetector;
     35 import com.android.inputmethod.keyboard.Keyboard;
     36 import com.android.inputmethod.keyboard.internal.KeyboardState;
     37 import com.android.inputmethod.latin.define.ProductionFlag;
     38 
     39 import java.io.BufferedWriter;
     40 import java.io.File;
     41 import java.io.FileInputStream;
     42 import java.io.FileWriter;
     43 import java.io.IOException;
     44 import java.io.PrintWriter;
     45 import java.nio.ByteBuffer;
     46 import java.nio.CharBuffer;
     47 import java.nio.channels.FileChannel;
     48 import java.nio.charset.Charset;
     49 import java.util.Map;
     50 
     51 /**
     52  * Logs the use of the LatinIME keyboard.
     53  *
     54  * This class logs operations on the IME keyboard, including what the user has typed.
     55  * Data is stored locally in a file in app-specific storage.
     56  *
     57  * This functionality is off by default. See {@link ProductionFlag#IS_EXPERIMENTAL}.
     58  */
     59 public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChangeListener {
     60     private static final String TAG = ResearchLogger.class.getSimpleName();
     61     private static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode";
     62     private static final boolean DEBUG = false;
     63 
     64     private static final ResearchLogger sInstance = new ResearchLogger(new LogFileManager());
     65     public static boolean sIsLogging = false;
     66     /* package */ final Handler mLoggingHandler;
     67     private InputMethodService mIms;
     68 
     69     /**
     70      * Isolates management of files. This variable should never be null, but can be changed
     71      * to support testing.
     72      */
     73     /* package */ LogFileManager mLogFileManager;
     74 
     75     /**
     76      * Manages the file(s) that stores the logs.
     77      *
     78      * Handles creation, deletion, and provides Readers, Writers, and InputStreams to access
     79      * the logs.
     80      */
     81     /* package */ static class LogFileManager {
     82         public static final String RESEARCH_LOG_FILENAME_KEY = "RESEARCH_LOG_FILENAME";
     83 
     84         private static final String DEFAULT_FILENAME = "researchLog.txt";
     85         private static final long LOGFILE_PURGE_INTERVAL = 1000 * 60 * 60 * 24;
     86 
     87         protected InputMethodService mIms;
     88         protected File mFile;
     89         protected PrintWriter mPrintWriter;
     90 
     91         /* package */ LogFileManager() {
     92         }
     93 
     94         public void init(final InputMethodService ims) {
     95             mIms = ims;
     96         }
     97 
     98         public synchronized void createLogFile() throws IOException {
     99             createLogFile(DEFAULT_FILENAME);
    100         }
    101 
    102         public synchronized void createLogFile(final SharedPreferences prefs)
    103                 throws IOException {
    104             final String filename =
    105                     prefs.getString(RESEARCH_LOG_FILENAME_KEY, DEFAULT_FILENAME);
    106             createLogFile(filename);
    107         }
    108 
    109         public synchronized void createLogFile(final String filename)
    110                 throws IOException {
    111             if (mIms == null) {
    112                 final String msg = "InputMethodService is not configured.  Logging is off.";
    113                 Log.w(TAG, msg);
    114                 throw new IOException(msg);
    115             }
    116             final File filesDir = mIms.getFilesDir();
    117             if (filesDir == null || !filesDir.exists()) {
    118                 final String msg = "Storage directory does not exist.  Logging is off.";
    119                 Log.w(TAG, msg);
    120                 throw new IOException(msg);
    121             }
    122             close();
    123             final File file = new File(filesDir, filename);
    124             mFile = file;
    125             boolean append = true;
    126             if (file.exists() && file.lastModified() + LOGFILE_PURGE_INTERVAL <
    127                     System.currentTimeMillis()) {
    128                 append = false;
    129             }
    130             mPrintWriter = new PrintWriter(new BufferedWriter(new FileWriter(file, append)), true);
    131         }
    132 
    133         public synchronized boolean append(final String s) {
    134             PrintWriter printWriter = mPrintWriter;
    135             if (printWriter == null || !mFile.exists()) {
    136                 if (DEBUG) {
    137                     Log.w(TAG, "PrintWriter is null... attempting to create default log file");
    138                 }
    139                 try {
    140                     createLogFile();
    141                     printWriter = mPrintWriter;
    142                 } catch (IOException e) {
    143                     Log.w(TAG, "Failed to create log file.  Not logging.");
    144                     return false;
    145                 }
    146             }
    147             printWriter.print(s);
    148             printWriter.flush();
    149             return !printWriter.checkError();
    150         }
    151 
    152         public synchronized void reset() {
    153             if (mPrintWriter != null) {
    154                 mPrintWriter.close();
    155                 mPrintWriter = null;
    156                 if (DEBUG) {
    157                     Log.d(TAG, "logfile closed");
    158                 }
    159             }
    160             if (mFile != null) {
    161                 mFile.delete();
    162                 if (DEBUG) {
    163                     Log.d(TAG, "logfile deleted");
    164                 }
    165                 mFile = null;
    166             }
    167         }
    168 
    169         public synchronized void close() {
    170             if (mPrintWriter != null) {
    171                 mPrintWriter.close();
    172                 mPrintWriter = null;
    173                 mFile = null;
    174                 if (DEBUG) {
    175                     Log.d(TAG, "logfile closed");
    176                 }
    177             }
    178         }
    179 
    180         /* package */ synchronized void flush() {
    181             if (mPrintWriter != null) {
    182                 mPrintWriter.flush();
    183             }
    184         }
    185 
    186         /* package */ synchronized String getContents() {
    187             final File file = mFile;
    188             if (file == null) {
    189                 return "";
    190             }
    191             if (mPrintWriter != null) {
    192                 mPrintWriter.flush();
    193             }
    194             FileInputStream stream = null;
    195             FileChannel fileChannel = null;
    196             String s = "";
    197             try {
    198                 stream = new FileInputStream(file);
    199                 fileChannel = stream.getChannel();
    200                 final ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());
    201                 fileChannel.read(byteBuffer);
    202                 byteBuffer.rewind();
    203                 CharBuffer charBuffer = Charset.defaultCharset().decode(byteBuffer);
    204                 s = charBuffer.toString();
    205             } catch (IOException e) {
    206                 e.printStackTrace();
    207             } finally {
    208                 try {
    209                     if (fileChannel != null) {
    210                         fileChannel.close();
    211                     }
    212                 } catch (IOException e) {
    213                     e.printStackTrace();
    214                 } finally {
    215                     try {
    216                         if (stream != null) {
    217                             stream.close();
    218                         }
    219                     } catch (IOException e) {
    220                         e.printStackTrace();
    221                     }
    222                 }
    223             }
    224             return s;
    225         }
    226     }
    227 
    228     private ResearchLogger(final LogFileManager logFileManager) {
    229         final HandlerThread handlerThread = new HandlerThread("ResearchLogger logging task",
    230                 Process.THREAD_PRIORITY_BACKGROUND);
    231         handlerThread.start();
    232         mLoggingHandler = new Handler(handlerThread.getLooper());
    233         mLogFileManager = logFileManager;
    234     }
    235 
    236     public static ResearchLogger getInstance() {
    237         return sInstance;
    238     }
    239 
    240     public static void init(final InputMethodService ims, final SharedPreferences prefs) {
    241         sInstance.initInternal(ims, prefs);
    242     }
    243 
    244     /* package */ void initInternal(final InputMethodService ims, final SharedPreferences prefs) {
    245         mIms = ims;
    246         final LogFileManager logFileManager = mLogFileManager;
    247         if (logFileManager != null) {
    248             logFileManager.init(ims);
    249             try {
    250                 logFileManager.createLogFile(prefs);
    251             } catch (IOException e) {
    252                 e.printStackTrace();
    253             }
    254         }
    255         if (prefs != null) {
    256             sIsLogging = prefs.getBoolean(PREF_USABILITY_STUDY_MODE, false);
    257             prefs.registerOnSharedPreferenceChangeListener(this);
    258         }
    259     }
    260 
    261     /**
    262      * Represents a category of logging events that share the same subfield structure.
    263      */
    264     private static enum LogGroup {
    265         MOTION_EVENT("m"),
    266         KEY("k"),
    267         CORRECTION("c"),
    268         STATE_CHANGE("s"),
    269         UNSTRUCTURED("u");
    270 
    271         private final String mLogString;
    272 
    273         private LogGroup(final String logString) {
    274             mLogString = logString;
    275         }
    276     }
    277 
    278     public void logMotionEvent(final int action, final long eventTime, final int id,
    279             final int x, final int y, final float size, final float pressure) {
    280         final String eventTag;
    281         switch (action) {
    282             case MotionEvent.ACTION_CANCEL: eventTag = "[Cancel]"; break;
    283             case MotionEvent.ACTION_UP: eventTag = "[Up]"; break;
    284             case MotionEvent.ACTION_DOWN: eventTag = "[Down]"; break;
    285             case MotionEvent.ACTION_POINTER_UP: eventTag = "[PointerUp]"; break;
    286             case MotionEvent.ACTION_POINTER_DOWN: eventTag = "[PointerDown]"; break;
    287             case MotionEvent.ACTION_MOVE: eventTag = "[Move]"; break;
    288             case MotionEvent.ACTION_OUTSIDE: eventTag = "[Outside]"; break;
    289             default: eventTag = "[Action" + action + "]"; break;
    290         }
    291         if (!TextUtils.isEmpty(eventTag)) {
    292             final StringBuilder sb = new StringBuilder();
    293             sb.append(eventTag);
    294             sb.append('\t'); sb.append(eventTime);
    295             sb.append('\t'); sb.append(id);
    296             sb.append('\t'); sb.append(x);
    297             sb.append('\t'); sb.append(y);
    298             sb.append('\t'); sb.append(size);
    299             sb.append('\t'); sb.append(pressure);
    300             write(LogGroup.MOTION_EVENT, sb.toString());
    301         }
    302     }
    303 
    304     public void logKeyEvent(final int code, final int x, final int y) {
    305         final StringBuilder sb = new StringBuilder();
    306         sb.append(Keyboard.printableCode(code));
    307         sb.append('\t'); sb.append(x);
    308         sb.append('\t'); sb.append(y);
    309         write(LogGroup.KEY, sb.toString());
    310     }
    311 
    312     public void logCorrection(final String subgroup, final String before, final String after,
    313             final int position) {
    314         final StringBuilder sb = new StringBuilder();
    315         sb.append(subgroup);
    316         sb.append('\t'); sb.append(before);
    317         sb.append('\t'); sb.append(after);
    318         sb.append('\t'); sb.append(position);
    319         write(LogGroup.CORRECTION, sb.toString());
    320     }
    321 
    322     public void logStateChange(final String subgroup, final String details) {
    323         write(LogGroup.STATE_CHANGE, subgroup + "\t" + details);
    324     }
    325 
    326     public static class UnsLogGroup {
    327         private static final boolean DEFAULT_ENABLED = true;
    328 
    329         private static final boolean KEYBOARDSTATE_ONCANCELINPUT_ENABLED = DEFAULT_ENABLED;
    330         private static final boolean KEYBOARDSTATE_ONCODEINPUT_ENABLED = DEFAULT_ENABLED;
    331         private static final boolean KEYBOARDSTATE_ONLONGPRESSTIMEOUT_ENABLED = DEFAULT_ENABLED;
    332         private static final boolean KEYBOARDSTATE_ONPRESSKEY_ENABLED = DEFAULT_ENABLED;
    333         private static final boolean KEYBOARDSTATE_ONRELEASEKEY_ENABLED = DEFAULT_ENABLED;
    334         private static final boolean LATINIME_COMMITCURRENTAUTOCORRECTION_ENABLED = DEFAULT_ENABLED;
    335         private static final boolean LATINIME_COMMITTEXT_ENABLED = DEFAULT_ENABLED;
    336         private static final boolean LATINIME_DELETESURROUNDINGTEXT_ENABLED = DEFAULT_ENABLED;
    337         private static final boolean LATINIME_DOUBLESPACEAUTOPERIOD_ENABLED = DEFAULT_ENABLED;
    338         private static final boolean LATINIME_ONDISPLAYCOMPLETIONS_ENABLED = DEFAULT_ENABLED;
    339         private static final boolean LATINIME_ONSTARTINPUTVIEWINTERNAL_ENABLED = DEFAULT_ENABLED;
    340         private static final boolean LATINIME_ONUPDATESELECTION_ENABLED = DEFAULT_ENABLED;
    341         private static final boolean LATINIME_PERFORMEDITORACTION_ENABLED = DEFAULT_ENABLED;
    342         private static final boolean LATINIME_PICKAPPLICATIONSPECIFIEDCOMPLETION_ENABLED
    343                 = DEFAULT_ENABLED;
    344         private static final boolean LATINIME_PICKPUNCTUATIONSUGGESTION_ENABLED = DEFAULT_ENABLED;
    345         private static final boolean LATINIME_PICKSUGGESTIONMANUALLY_ENABLED = DEFAULT_ENABLED;
    346         private static final boolean LATINIME_REVERTCOMMIT_ENABLED = DEFAULT_ENABLED;
    347         private static final boolean LATINIME_REVERTDOUBLESPACEWHILEINBATCHEDIT_ENABLED
    348                 = DEFAULT_ENABLED;
    349         private static final boolean LATINIME_REVERTSWAPPUNCTUATION_ENABLED = DEFAULT_ENABLED;
    350         private static final boolean LATINIME_SENDKEYCODEPOINT_ENABLED = DEFAULT_ENABLED;
    351         private static final boolean LATINIME_SWAPSWAPPERANDSPACEWHILEINBATCHEDIT_ENABLED
    352                 = DEFAULT_ENABLED;
    353         private static final boolean LATINIME_SWITCHTOKEYBOARDVIEW_ENABLED = DEFAULT_ENABLED;
    354         private static final boolean LATINKEYBOARDVIEW_ONLONGPRESS_ENABLED = DEFAULT_ENABLED;
    355         private static final boolean LATINKEYBOARDVIEW_ONPROCESSMOTIONEVENT_ENABLED
    356                 = DEFAULT_ENABLED;
    357         private static final boolean LATINKEYBOARDVIEW_SETKEYBOARD_ENABLED = DEFAULT_ENABLED;
    358         private static final boolean POINTERTRACKER_CALLLISTENERONCANCELINPUT_ENABLED
    359                 = DEFAULT_ENABLED;
    360         private static final boolean POINTERTRACKER_CALLLISTENERONCODEINPUT_ENABLED
    361                 = DEFAULT_ENABLED;
    362         private static final boolean
    363                 POINTERTRACKER_CALLLISTENERONPRESSANDCHECKKEYBOARDLAYOUTCHANGE_ENABLED
    364                 = DEFAULT_ENABLED;
    365         private static final boolean POINTERTRACKER_CALLLISTENERONRELEASE_ENABLED = DEFAULT_ENABLED;
    366         private static final boolean POINTERTRACKER_ONDOWNEVENT_ENABLED = DEFAULT_ENABLED;
    367         private static final boolean POINTERTRACKER_ONMOVEEVENT_ENABLED = DEFAULT_ENABLED;
    368         private static final boolean SUDDENJUMPINGTOUCHEVENTHANDLER_ONTOUCHEVENT_ENABLED
    369                 = DEFAULT_ENABLED;
    370         private static final boolean SUGGESTIONSVIEW_SETSUGGESTIONS_ENABLED = DEFAULT_ENABLED;
    371     }
    372 
    373     public static void logUnstructured(String logGroup, final String details) {
    374         // TODO: improve performance by making entire class static and/or implementing natively
    375         getInstance().write(LogGroup.UNSTRUCTURED, logGroup + "\t" + details);
    376     }
    377 
    378     private void write(final LogGroup logGroup, final String log) {
    379         // TODO: rewrite in native for better performance
    380         mLoggingHandler.post(new Runnable() {
    381             @Override
    382             public void run() {
    383                 final long currentTime = System.currentTimeMillis();
    384                 final long upTime = SystemClock.uptimeMillis();
    385                 final StringBuilder builder = new StringBuilder();
    386                 builder.append(currentTime);
    387                 builder.append('\t'); builder.append(upTime);
    388                 builder.append('\t'); builder.append(logGroup.mLogString);
    389                 builder.append('\t'); builder.append(log);
    390                 builder.append('\n');
    391                 if (DEBUG) {
    392                     Log.d(TAG, "Write: " + '[' + logGroup.mLogString + ']' + log);
    393                 }
    394                 final String s = builder.toString();
    395                 if (mLogFileManager.append(s)) {
    396                     // success
    397                 } else {
    398                     if (DEBUG) {
    399                         Log.w(TAG, "Unable to write to log.");
    400                     }
    401                     // perhaps logfile was deleted.  try to recreate and relog.
    402                     try {
    403                         mLogFileManager.createLogFile(PreferenceManager
    404                                 .getDefaultSharedPreferences(mIms));
    405                         mLogFileManager.append(s);
    406                     } catch (IOException e) {
    407                         e.printStackTrace();
    408                     }
    409                 }
    410             }
    411         });
    412     }
    413 
    414     public void clearAll() {
    415         mLoggingHandler.post(new Runnable() {
    416             @Override
    417             public void run() {
    418                 if (DEBUG) {
    419                     Log.d(TAG, "Delete log file.");
    420                 }
    421                 mLogFileManager.reset();
    422             }
    423         });
    424     }
    425 
    426     /* package */ LogFileManager getLogFileManager() {
    427         return mLogFileManager;
    428     }
    429 
    430     @Override
    431     public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
    432         if (key == null || prefs == null) {
    433             return;
    434         }
    435         sIsLogging = prefs.getBoolean(PREF_USABILITY_STUDY_MODE, false);
    436     }
    437 
    438     public static void keyboardState_onCancelInput(final boolean isSinglePointer,
    439             final KeyboardState keyboardState) {
    440         if (UnsLogGroup.KEYBOARDSTATE_ONCANCELINPUT_ENABLED) {
    441             final String s = "onCancelInput: single=" + isSinglePointer + " " + keyboardState;
    442             logUnstructured("KeyboardState_onCancelInput", s);
    443         }
    444     }
    445 
    446     public static void keyboardState_onCodeInput(
    447             final int code, final boolean isSinglePointer, final int autoCaps,
    448             final KeyboardState keyboardState) {
    449         if (UnsLogGroup.KEYBOARDSTATE_ONCODEINPUT_ENABLED) {
    450             final String s = "onCodeInput: code=" + Keyboard.printableCode(code)
    451                     + " single=" + isSinglePointer
    452                     + " autoCaps=" + autoCaps + " " + keyboardState;
    453             logUnstructured("KeyboardState_onCodeInput", s);
    454         }
    455     }
    456 
    457     public static void keyboardState_onLongPressTimeout(final int code,
    458             final KeyboardState keyboardState) {
    459         if (UnsLogGroup.KEYBOARDSTATE_ONLONGPRESSTIMEOUT_ENABLED) {
    460             final String s = "onLongPressTimeout: code=" + Keyboard.printableCode(code) + " "
    461                     + keyboardState;
    462             logUnstructured("KeyboardState_onLongPressTimeout", s);
    463         }
    464     }
    465 
    466     public static void keyboardState_onPressKey(final int code,
    467             final KeyboardState keyboardState) {
    468         if (UnsLogGroup.KEYBOARDSTATE_ONPRESSKEY_ENABLED) {
    469             final String s = "onPressKey: code=" + Keyboard.printableCode(code) + " "
    470                     + keyboardState;
    471             logUnstructured("KeyboardState_onPressKey", s);
    472         }
    473     }
    474 
    475     public static void keyboardState_onReleaseKey(final KeyboardState keyboardState, final int code,
    476             final boolean withSliding) {
    477         if (UnsLogGroup.KEYBOARDSTATE_ONRELEASEKEY_ENABLED) {
    478             final String s = "onReleaseKey: code=" + Keyboard.printableCode(code)
    479                     + " sliding=" + withSliding + " " + keyboardState;
    480             logUnstructured("KeyboardState_onReleaseKey", s);
    481         }
    482     }
    483 
    484     public static void latinIME_commitCurrentAutoCorrection(final String typedWord,
    485             final String autoCorrection) {
    486         if (UnsLogGroup.LATINIME_COMMITCURRENTAUTOCORRECTION_ENABLED) {
    487             if (typedWord.equals(autoCorrection)) {
    488                 getInstance().logCorrection("[----]", typedWord, autoCorrection, -1);
    489             } else {
    490                 getInstance().logCorrection("[Auto]", typedWord, autoCorrection, -1);
    491             }
    492         }
    493     }
    494 
    495     public static void latinIME_commitText(final CharSequence typedWord) {
    496         if (UnsLogGroup.LATINIME_COMMITTEXT_ENABLED) {
    497             logUnstructured("LatinIME_commitText", typedWord.toString());
    498         }
    499     }
    500 
    501     public static void latinIME_deleteSurroundingText(final int length) {
    502         if (UnsLogGroup.LATINIME_DELETESURROUNDINGTEXT_ENABLED) {
    503             logUnstructured("LatinIME_deleteSurroundingText", String.valueOf(length));
    504         }
    505     }
    506 
    507     public static void latinIME_doubleSpaceAutoPeriod() {
    508         if (UnsLogGroup.LATINIME_DOUBLESPACEAUTOPERIOD_ENABLED) {
    509             logUnstructured("LatinIME_doubleSpaceAutoPeriod", "");
    510         }
    511     }
    512 
    513     public static void latinIME_onDisplayCompletions(
    514             final CompletionInfo[] applicationSpecifiedCompletions) {
    515         if (UnsLogGroup.LATINIME_ONDISPLAYCOMPLETIONS_ENABLED) {
    516             final StringBuilder builder = new StringBuilder();
    517             builder.append("Received completions:");
    518             if (applicationSpecifiedCompletions != null) {
    519                 for (int i = 0; i < applicationSpecifiedCompletions.length; i++) {
    520                     builder.append("  #");
    521                     builder.append(i);
    522                     builder.append(": ");
    523                     builder.append(applicationSpecifiedCompletions[i]);
    524                     builder.append("\n");
    525                 }
    526             }
    527             logUnstructured("LatinIME_onDisplayCompletions", builder.toString());
    528         }
    529     }
    530 
    531     public static void latinIME_onStartInputViewInternal(final EditorInfo editorInfo,
    532             final SharedPreferences prefs) {
    533         if (UnsLogGroup.LATINIME_ONSTARTINPUTVIEWINTERNAL_ENABLED) {
    534             final StringBuilder builder = new StringBuilder();
    535             builder.append("onStartInputView: editorInfo:");
    536             builder.append("\tinputType=");
    537             builder.append(Integer.toHexString(editorInfo.inputType));
    538             builder.append("\timeOptions=");
    539             builder.append(Integer.toHexString(editorInfo.imeOptions));
    540             builder.append("\tdisplay="); builder.append(Build.DISPLAY);
    541             builder.append("\tmodel="); builder.append(Build.MODEL);
    542             for (Map.Entry<String,?> entry : prefs.getAll().entrySet()) {
    543                 builder.append("\t" + entry.getKey());
    544                 Object value = entry.getValue();
    545                 builder.append("=" + ((value == null) ? "<null>" : value.toString()));
    546             }
    547             logUnstructured("LatinIME_onStartInputViewInternal", builder.toString());
    548         }
    549     }
    550 
    551     public static void latinIME_onUpdateSelection(final int lastSelectionStart,
    552             final int lastSelectionEnd, final int oldSelStart, final int oldSelEnd,
    553             final int newSelStart, final int newSelEnd, final int composingSpanStart,
    554             final int composingSpanEnd) {
    555         if (UnsLogGroup.LATINIME_ONUPDATESELECTION_ENABLED) {
    556             final String s = "onUpdateSelection: oss=" + oldSelStart
    557                     + ", ose=" + oldSelEnd
    558                     + ", lss=" + lastSelectionStart
    559                     + ", lse=" + lastSelectionEnd
    560                     + ", nss=" + newSelStart
    561                     + ", nse=" + newSelEnd
    562                     + ", cs=" + composingSpanStart
    563                     + ", ce=" + composingSpanEnd;
    564             logUnstructured("LatinIME_onUpdateSelection", s);
    565         }
    566     }
    567 
    568     public static void latinIME_performEditorAction(final int imeActionNext) {
    569         if (UnsLogGroup.LATINIME_PERFORMEDITORACTION_ENABLED) {
    570             logUnstructured("LatinIME_performEditorAction", String.valueOf(imeActionNext));
    571         }
    572     }
    573 
    574     public static void latinIME_pickApplicationSpecifiedCompletion(final int index,
    575             final CharSequence text, int x, int y) {
    576         if (UnsLogGroup.LATINIME_PICKAPPLICATIONSPECIFIEDCOMPLETION_ENABLED) {
    577             final String s = String.valueOf(index) + '\t' + text + '\t' + x + '\t' + y;
    578             logUnstructured("LatinIME_pickApplicationSpecifiedCompletion", s);
    579         }
    580     }
    581 
    582     public static void latinIME_pickSuggestionManually(final String replacedWord,
    583             final int index, CharSequence suggestion, int x, int y) {
    584         if (UnsLogGroup.LATINIME_PICKSUGGESTIONMANUALLY_ENABLED) {
    585             final String s = String.valueOf(index) + '\t' + suggestion + '\t' + x + '\t' + y;
    586             logUnstructured("LatinIME_pickSuggestionManually", s);
    587         }
    588     }
    589 
    590     public static void latinIME_punctuationSuggestion(final int index,
    591             final CharSequence suggestion, int x, int y) {
    592         if (UnsLogGroup.LATINIME_PICKPUNCTUATIONSUGGESTION_ENABLED) {
    593             final String s = String.valueOf(index) + '\t' + suggestion + '\t' + x + '\t' + y;
    594             logUnstructured("LatinIME_pickPunctuationSuggestion", s);
    595         }
    596     }
    597 
    598     public static void latinIME_revertDoubleSpaceWhileInBatchEdit() {
    599         if (UnsLogGroup.LATINIME_REVERTDOUBLESPACEWHILEINBATCHEDIT_ENABLED) {
    600             logUnstructured("LatinIME_revertDoubleSpaceWhileInBatchEdit", "");
    601         }
    602     }
    603 
    604     public static void latinIME_revertSwapPunctuation() {
    605         if (UnsLogGroup.LATINIME_REVERTSWAPPUNCTUATION_ENABLED) {
    606             logUnstructured("LatinIME_revertSwapPunctuation", "");
    607         }
    608     }
    609 
    610     public static void latinIME_sendKeyCodePoint(final int code) {
    611         if (UnsLogGroup.LATINIME_SENDKEYCODEPOINT_ENABLED) {
    612             logUnstructured("LatinIME_sendKeyCodePoint", String.valueOf(code));
    613         }
    614     }
    615 
    616     public static void latinIME_swapSwapperAndSpaceWhileInBatchEdit() {
    617         if (UnsLogGroup.LATINIME_SWAPSWAPPERANDSPACEWHILEINBATCHEDIT_ENABLED) {
    618             logUnstructured("latinIME_swapSwapperAndSpaceWhileInBatchEdit", "");
    619         }
    620     }
    621 
    622     public static void latinIME_switchToKeyboardView() {
    623         if (UnsLogGroup.LATINIME_SWITCHTOKEYBOARDVIEW_ENABLED) {
    624             final String s = "Switch to keyboard view.";
    625             logUnstructured("LatinIME_switchToKeyboardView", s);
    626         }
    627     }
    628 
    629     public static void latinKeyboardView_onLongPress() {
    630         if (UnsLogGroup.LATINKEYBOARDVIEW_ONLONGPRESS_ENABLED) {
    631             final String s = "long press detected";
    632             logUnstructured("LatinKeyboardView_onLongPress", s);
    633         }
    634     }
    635 
    636     public static void latinKeyboardView_processMotionEvent(MotionEvent me, int action,
    637             long eventTime, int index, int id, int x, int y) {
    638         if (UnsLogGroup.LATINKEYBOARDVIEW_ONPROCESSMOTIONEVENT_ENABLED) {
    639             final float size = me.getSize(index);
    640             final float pressure = me.getPressure(index);
    641             if (action != MotionEvent.ACTION_MOVE) {
    642                 getInstance().logMotionEvent(action, eventTime, id, x, y, size, pressure);
    643             }
    644         }
    645     }
    646 
    647     public static void latinKeyboardView_setKeyboard(final Keyboard keyboard) {
    648         if (UnsLogGroup.LATINKEYBOARDVIEW_SETKEYBOARD_ENABLED) {
    649             StringBuilder builder = new StringBuilder();
    650             builder.append("id=");
    651             builder.append(keyboard.mId);
    652             builder.append("\tw=");
    653             builder.append(keyboard.mOccupiedWidth);
    654             builder.append("\th=");
    655             builder.append(keyboard.mOccupiedHeight);
    656             builder.append("\tkeys=[");
    657             boolean first = true;
    658             for (Key key : keyboard.mKeys) {
    659                 if (first) {
    660                     first = false;
    661                 } else {
    662                     builder.append(",");
    663                 }
    664                 builder.append("{code:");
    665                 builder.append(key.mCode);
    666                 builder.append(",altCode:");
    667                 builder.append(key.mAltCode);
    668                 builder.append(",x:");
    669                 builder.append(key.mX);
    670                 builder.append(",y:");
    671                 builder.append(key.mY);
    672                 builder.append(",w:");
    673                 builder.append(key.mWidth);
    674                 builder.append(",h:");
    675                 builder.append(key.mHeight);
    676                 builder.append("}");
    677             }
    678             builder.append("]");
    679             logUnstructured("LatinKeyboardView_setKeyboard", builder.toString());
    680         }
    681     }
    682 
    683     public static void latinIME_revertCommit(final String originallyTypedWord) {
    684         if (UnsLogGroup.LATINIME_REVERTCOMMIT_ENABLED) {
    685             logUnstructured("LatinIME_revertCommit", originallyTypedWord);
    686         }
    687     }
    688 
    689     public static void pointerTracker_callListenerOnCancelInput() {
    690         final String s = "onCancelInput";
    691         if (UnsLogGroup.POINTERTRACKER_CALLLISTENERONCANCELINPUT_ENABLED) {
    692             logUnstructured("PointerTracker_callListenerOnCancelInput", s);
    693         }
    694     }
    695 
    696     public static void pointerTracker_callListenerOnCodeInput(final Key key, final int x,
    697             final int y, final boolean ignoreModifierKey, final boolean altersCode,
    698             final int code) {
    699         if (UnsLogGroup.POINTERTRACKER_CALLLISTENERONCODEINPUT_ENABLED) {
    700             final String s = "onCodeInput: " + Keyboard.printableCode(code)
    701                     + " text=" + key.mOutputText + " x=" + x + " y=" + y
    702                     + " ignoreModifier=" + ignoreModifierKey + " altersCode=" + altersCode
    703                     + " enabled=" + key.isEnabled();
    704             logUnstructured("PointerTracker_callListenerOnCodeInput", s);
    705         }
    706     }
    707 
    708     public static void pointerTracker_callListenerOnPressAndCheckKeyboardLayoutChange(
    709             final Key key, final boolean ignoreModifierKey) {
    710         if (UnsLogGroup.POINTERTRACKER_CALLLISTENERONPRESSANDCHECKKEYBOARDLAYOUTCHANGE_ENABLED) {
    711             final String s = "onPress    : " + KeyDetector.printableCode(key)
    712                     + " ignoreModifier=" + ignoreModifierKey
    713                     + " enabled=" + key.isEnabled();
    714             logUnstructured("PointerTracker_callListenerOnPressAndCheckKeyboardLayoutChange", s);
    715         }
    716     }
    717 
    718     public static void pointerTracker_callListenerOnRelease(final Key key, final int primaryCode,
    719             final boolean withSliding, final boolean ignoreModifierKey) {
    720         if (UnsLogGroup.POINTERTRACKER_CALLLISTENERONRELEASE_ENABLED) {
    721             final String s = "onRelease  : " + Keyboard.printableCode(primaryCode)
    722                     + " sliding=" + withSliding + " ignoreModifier=" + ignoreModifierKey
    723                     + " enabled="+ key.isEnabled();
    724             logUnstructured("PointerTracker_callListenerOnRelease", s);
    725         }
    726     }
    727 
    728     public static void pointerTracker_onDownEvent(long deltaT, int distanceSquared) {
    729         if (UnsLogGroup.POINTERTRACKER_ONDOWNEVENT_ENABLED) {
    730             final String s = "onDownEvent: ignore potential noise: time=" + deltaT
    731                     + " distance=" + distanceSquared;
    732             logUnstructured("PointerTracker_onDownEvent", s);
    733         }
    734     }
    735 
    736     public static void pointerTracker_onMoveEvent(final int x, final int y, final int lastX,
    737             final int lastY) {
    738         if (UnsLogGroup.POINTERTRACKER_ONMOVEEVENT_ENABLED) {
    739             final String s = String.format("onMoveEvent: sudden move is translated to "
    740                     + "up[%d,%d]/down[%d,%d] events", lastX, lastY, x, y);
    741             logUnstructured("PointerTracker_onMoveEvent", s);
    742         }
    743     }
    744 
    745     public static void suddenJumpingTouchEventHandler_onTouchEvent(final MotionEvent me) {
    746         if (UnsLogGroup.SUDDENJUMPINGTOUCHEVENTHANDLER_ONTOUCHEVENT_ENABLED) {
    747             final String s = "onTouchEvent: ignore sudden jump " + me;
    748             logUnstructured("SuddenJumpingTouchEventHandler_onTouchEvent", s);
    749         }
    750     }
    751 
    752     public static void suggestionsView_setSuggestions(final SuggestedWords mSuggestedWords) {
    753         if (UnsLogGroup.SUGGESTIONSVIEW_SETSUGGESTIONS_ENABLED) {
    754             logUnstructured("SuggestionsView_setSuggestions", mSuggestedWords.toString());
    755         }
    756     }
    757 }