Home | History | Annotate | Download | only in research
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of 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,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.inputmethod.research;
     18 
     19 import android.util.Log;
     20 
     21 import com.android.inputmethod.latin.Constants;
     22 import com.android.inputmethod.latin.define.ProductionFlag;
     23 
     24 import java.util.concurrent.TimeUnit;
     25 
     26 public class Statistics {
     27     private static final String TAG = Statistics.class.getSimpleName();
     28     private static final boolean DEBUG = false
     29             && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
     30 
     31     // TODO: Cleanup comments to only including those giving meaningful information.
     32     // Number of characters entered during a typing session
     33     int mCharCount;
     34     // Number of letter characters entered during a typing session
     35     int mLetterCount;
     36     // Number of number characters entered
     37     int mNumberCount;
     38     // Number of space characters entered
     39     int mSpaceCount;
     40     // Number of delete operations entered (taps on the backspace key)
     41     int mDeleteKeyCount;
     42     // Number of words entered during a session.
     43     int mWordCount;
     44     // Number of words found in the dictionary.
     45     int mDictionaryWordCount;
     46     // Number of words split and spaces automatically entered.
     47     int mSplitWordsCount;
     48     // Number of words entered during a session.
     49     int mCorrectedWordsCount;
     50     // Number of gestures that were input.
     51     int mGesturesInputCount;
     52     // Number of gestures that were deleted.
     53     int mGesturesDeletedCount;
     54     // Total number of characters in words entered by gesture.
     55     int mGesturesCharsCount;
     56     // Number of manual suggestions chosen.
     57     int mManualSuggestionsCount;
     58     // Number of times that autocorrection was invoked.
     59     int mAutoCorrectionsCount;
     60     // Number of times a commit was reverted in this session.
     61     int mRevertCommitsCount;
     62     // Whether the text field was empty upon editing
     63     boolean mIsEmptyUponStarting;
     64     boolean mIsEmptinessStateKnown;
     65 
     66     // Counts of how often an n-gram is collected or not, and the reasons for the decision.
     67     // Keep consistent with publishability result code list in MainLogBuffer
     68     int mPublishableCount;
     69     int mUnpublishableStoppingCount;
     70     int mUnpublishableIncorrectWordCount;
     71     int mUnpublishableSampledTooRecently;
     72     int mUnpublishableDictionaryUnavailable;
     73     int mUnpublishableMayContainDigit;
     74     int mUnpublishableNotInDictionary;
     75 
     76     // Timers to count average time to enter a key, first press a delete key,
     77     // between delete keys, and then to return typing after a delete key.
     78     final AverageTimeCounter mKeyCounter = new AverageTimeCounter();
     79     final AverageTimeCounter mBeforeDeleteKeyCounter = new AverageTimeCounter();
     80     final AverageTimeCounter mDuringRepeatedDeleteKeysCounter = new AverageTimeCounter();
     81     final AverageTimeCounter mAfterDeleteKeyCounter = new AverageTimeCounter();
     82 
     83     static class AverageTimeCounter {
     84         int mCount;
     85         int mTotalTime;
     86 
     87         public void reset() {
     88             mCount = 0;
     89             mTotalTime = 0;
     90         }
     91 
     92         public void add(long deltaTime) {
     93             mCount++;
     94             mTotalTime += deltaTime;
     95         }
     96 
     97         public int getAverageTime() {
     98             if (mCount == 0) {
     99                 return 0;
    100             }
    101             return mTotalTime / mCount;
    102         }
    103     }
    104 
    105     // To account for the interruptions when the user's attention is directed elsewhere, times
    106     // longer than MIN_TYPING_INTERMISSION are not counted when estimating this statistic.
    107     public static final long MIN_TYPING_INTERMISSION = TimeUnit.SECONDS.toMillis(2);
    108     public static final long MIN_DELETION_INTERMISSION = TimeUnit.SECONDS.toMillis(10);
    109 
    110     // The last time that a tap was performed
    111     private long mLastTapTime;
    112     // The type of the last keypress (delete key or not)
    113     boolean mIsLastKeyDeleteKey;
    114 
    115     private static final Statistics sInstance = new Statistics();
    116 
    117     public static Statistics getInstance() {
    118         return sInstance;
    119     }
    120 
    121     private Statistics() {
    122         reset();
    123     }
    124 
    125     public void reset() {
    126         mCharCount = 0;
    127         mLetterCount = 0;
    128         mNumberCount = 0;
    129         mSpaceCount = 0;
    130         mDeleteKeyCount = 0;
    131         mWordCount = 0;
    132         mDictionaryWordCount = 0;
    133         mSplitWordsCount = 0;
    134         mCorrectedWordsCount = 0;
    135         mGesturesInputCount = 0;
    136         mGesturesDeletedCount = 0;
    137         mManualSuggestionsCount = 0;
    138         mRevertCommitsCount = 0;
    139         mAutoCorrectionsCount = 0;
    140         mIsEmptyUponStarting = true;
    141         mIsEmptinessStateKnown = false;
    142         mKeyCounter.reset();
    143         mBeforeDeleteKeyCounter.reset();
    144         mDuringRepeatedDeleteKeysCounter.reset();
    145         mAfterDeleteKeyCounter.reset();
    146         mGesturesCharsCount = 0;
    147         mGesturesDeletedCount = 0;
    148         mPublishableCount = 0;
    149         mUnpublishableStoppingCount = 0;
    150         mUnpublishableIncorrectWordCount = 0;
    151         mUnpublishableSampledTooRecently = 0;
    152         mUnpublishableDictionaryUnavailable = 0;
    153         mUnpublishableMayContainDigit = 0;
    154         mUnpublishableNotInDictionary = 0;
    155 
    156         mLastTapTime = 0;
    157         mIsLastKeyDeleteKey = false;
    158     }
    159 
    160     public void recordChar(int codePoint, long time) {
    161         if (DEBUG) {
    162             Log.d(TAG, "recordChar() called");
    163         }
    164         if (codePoint == Constants.CODE_DELETE) {
    165             mDeleteKeyCount++;
    166             recordUserAction(time, true /* isDeletion */);
    167         } else {
    168             mCharCount++;
    169             if (Character.isDigit(codePoint)) {
    170                 mNumberCount++;
    171             }
    172             if (Character.isLetter(codePoint)) {
    173                 mLetterCount++;
    174             }
    175             if (Character.isSpaceChar(codePoint)) {
    176                 mSpaceCount++;
    177             }
    178             recordUserAction(time, false /* isDeletion */);
    179         }
    180     }
    181 
    182     public void recordWordEntered(final boolean isDictionaryWord,
    183             final boolean containsCorrection) {
    184         mWordCount++;
    185         if (isDictionaryWord) {
    186             mDictionaryWordCount++;
    187         }
    188         if (containsCorrection) {
    189             mCorrectedWordsCount++;
    190         }
    191     }
    192 
    193     public void recordSplitWords() {
    194         mSplitWordsCount++;
    195     }
    196 
    197     public void recordGestureInput(final int numCharsEntered, final long time) {
    198         mGesturesInputCount++;
    199         mGesturesCharsCount += numCharsEntered;
    200         recordUserAction(time, false /* isDeletion */);
    201     }
    202 
    203     public void setIsEmptyUponStarting(final boolean isEmpty) {
    204         mIsEmptyUponStarting = isEmpty;
    205         mIsEmptinessStateKnown = true;
    206     }
    207 
    208     public void recordGestureDelete(final int length, final long time) {
    209         mGesturesDeletedCount++;
    210         recordUserAction(time, true /* isDeletion */);
    211     }
    212 
    213     public void recordManualSuggestion(final long time) {
    214         mManualSuggestionsCount++;
    215         recordUserAction(time, false /* isDeletion */);
    216     }
    217 
    218     public void recordAutoCorrection(final long time) {
    219         mAutoCorrectionsCount++;
    220         recordUserAction(time, false /* isDeletion */);
    221     }
    222 
    223     public void recordRevertCommit(final long time) {
    224         mRevertCommitsCount++;
    225         recordUserAction(time, true /* isDeletion */);
    226     }
    227 
    228     private void recordUserAction(final long time, final boolean isDeletion) {
    229         final long delta = time - mLastTapTime;
    230         if (isDeletion) {
    231             if (delta < MIN_DELETION_INTERMISSION) {
    232                 if (mIsLastKeyDeleteKey) {
    233                     mDuringRepeatedDeleteKeysCounter.add(delta);
    234                 } else {
    235                     mBeforeDeleteKeyCounter.add(delta);
    236                 }
    237             } else {
    238                 ResearchLogger.onUserPause(delta);
    239             }
    240         } else {
    241             if (mIsLastKeyDeleteKey && delta < MIN_DELETION_INTERMISSION) {
    242                 mAfterDeleteKeyCounter.add(delta);
    243             } else if (!mIsLastKeyDeleteKey && delta < MIN_TYPING_INTERMISSION) {
    244                 mKeyCounter.add(delta);
    245             } else {
    246                 ResearchLogger.onUserPause(delta);
    247             }
    248         }
    249         mIsLastKeyDeleteKey = isDeletion;
    250         mLastTapTime = time;
    251     }
    252 
    253     public void recordPublishabilityResultCode(final int publishabilityResultCode) {
    254         // Keep consistent with publishability result code list in MainLogBuffer
    255         switch (publishabilityResultCode) {
    256         case MainLogBuffer.PUBLISHABILITY_PUBLISHABLE:
    257             mPublishableCount++;
    258             break;
    259         case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_STOPPING:
    260             mUnpublishableStoppingCount++;
    261             break;
    262         case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_INCORRECT_WORD_COUNT:
    263             mUnpublishableIncorrectWordCount++;
    264             break;
    265         case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_SAMPLED_TOO_RECENTLY:
    266             mUnpublishableSampledTooRecently++;
    267             break;
    268         case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_DICTIONARY_UNAVAILABLE:
    269             mUnpublishableDictionaryUnavailable++;
    270             break;
    271         case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_MAY_CONTAIN_DIGIT:
    272             mUnpublishableMayContainDigit++;
    273             break;
    274         case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_NOT_IN_DICTIONARY:
    275             mUnpublishableNotInDictionary++;
    276             break;
    277         }
    278     }
    279 }
    280