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 public class Statistics {
     25     private static final String TAG = Statistics.class.getSimpleName();
     26     private static final boolean DEBUG = false
     27             && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
     28 
     29     // TODO: Cleanup comments to only including those giving meaningful information.
     30     // Number of characters entered during a typing session
     31     int mCharCount;
     32     // Number of letter characters entered during a typing session
     33     int mLetterCount;
     34     // Number of number characters entered
     35     int mNumberCount;
     36     // Number of space characters entered
     37     int mSpaceCount;
     38     // Number of delete operations entered (taps on the backspace key)
     39     int mDeleteKeyCount;
     40     // Number of words entered during a session.
     41     int mWordCount;
     42     // Number of words found in the dictionary.
     43     int mDictionaryWordCount;
     44     // Number of words split and spaces automatically entered.
     45     int mSplitWordsCount;
     46     // Number of words entered during a session.
     47     int mCorrectedWordsCount;
     48     // Number of gestures that were input.
     49     int mGesturesInputCount;
     50     // Number of gestures that were deleted.
     51     int mGesturesDeletedCount;
     52     // Total number of characters in words entered by gesture.
     53     int mGesturesCharsCount;
     54     // Number of manual suggestions chosen.
     55     int mManualSuggestionsCount;
     56     // Number of times that autocorrection was invoked.
     57     int mAutoCorrectionsCount;
     58     // Number of times a commit was reverted in this session.
     59     int mRevertCommitsCount;
     60     // Whether the text field was empty upon editing
     61     boolean mIsEmptyUponStarting;
     62     boolean mIsEmptinessStateKnown;
     63 
     64     // Timers to count average time to enter a key, first press a delete key,
     65     // between delete keys, and then to return typing after a delete key.
     66     final AverageTimeCounter mKeyCounter = new AverageTimeCounter();
     67     final AverageTimeCounter mBeforeDeleteKeyCounter = new AverageTimeCounter();
     68     final AverageTimeCounter mDuringRepeatedDeleteKeysCounter = new AverageTimeCounter();
     69     final AverageTimeCounter mAfterDeleteKeyCounter = new AverageTimeCounter();
     70 
     71     static class AverageTimeCounter {
     72         int mCount;
     73         int mTotalTime;
     74 
     75         public void reset() {
     76             mCount = 0;
     77             mTotalTime = 0;
     78         }
     79 
     80         public void add(long deltaTime) {
     81             mCount++;
     82             mTotalTime += deltaTime;
     83         }
     84 
     85         public int getAverageTime() {
     86             if (mCount == 0) {
     87                 return 0;
     88             }
     89             return mTotalTime / mCount;
     90         }
     91     }
     92 
     93     // To account for the interruptions when the user's attention is directed elsewhere, times
     94     // longer than MIN_TYPING_INTERMISSION are not counted when estimating this statistic.
     95     public static final int MIN_TYPING_INTERMISSION = 2 * 1000;  // in milliseconds
     96     public static final int MIN_DELETION_INTERMISSION = 10 * 1000;  // in milliseconds
     97 
     98     // The last time that a tap was performed
     99     private long mLastTapTime;
    100     // The type of the last keypress (delete key or not)
    101     boolean mIsLastKeyDeleteKey;
    102 
    103     private static final Statistics sInstance = new Statistics();
    104 
    105     public static Statistics getInstance() {
    106         return sInstance;
    107     }
    108 
    109     private Statistics() {
    110         reset();
    111     }
    112 
    113     public void reset() {
    114         mCharCount = 0;
    115         mLetterCount = 0;
    116         mNumberCount = 0;
    117         mSpaceCount = 0;
    118         mDeleteKeyCount = 0;
    119         mWordCount = 0;
    120         mDictionaryWordCount = 0;
    121         mSplitWordsCount = 0;
    122         mCorrectedWordsCount = 0;
    123         mGesturesInputCount = 0;
    124         mGesturesDeletedCount = 0;
    125         mManualSuggestionsCount = 0;
    126         mRevertCommitsCount = 0;
    127         mAutoCorrectionsCount = 0;
    128         mIsEmptyUponStarting = true;
    129         mIsEmptinessStateKnown = false;
    130         mKeyCounter.reset();
    131         mBeforeDeleteKeyCounter.reset();
    132         mDuringRepeatedDeleteKeysCounter.reset();
    133         mAfterDeleteKeyCounter.reset();
    134         mGesturesCharsCount = 0;
    135         mGesturesDeletedCount = 0;
    136 
    137         mLastTapTime = 0;
    138         mIsLastKeyDeleteKey = false;
    139     }
    140 
    141     public void recordChar(int codePoint, long time) {
    142         if (DEBUG) {
    143             Log.d(TAG, "recordChar() called");
    144         }
    145         if (codePoint == Constants.CODE_DELETE) {
    146             mDeleteKeyCount++;
    147             recordUserAction(time, true /* isDeletion */);
    148         } else {
    149             mCharCount++;
    150             if (Character.isDigit(codePoint)) {
    151                 mNumberCount++;
    152             }
    153             if (Character.isLetter(codePoint)) {
    154                 mLetterCount++;
    155             }
    156             if (Character.isSpaceChar(codePoint)) {
    157                 mSpaceCount++;
    158             }
    159             recordUserAction(time, false /* isDeletion */);
    160         }
    161     }
    162 
    163     public void recordWordEntered(final boolean isDictionaryWord,
    164             final boolean containsCorrection) {
    165         mWordCount++;
    166         if (isDictionaryWord) {
    167             mDictionaryWordCount++;
    168         }
    169         if (containsCorrection) {
    170             mCorrectedWordsCount++;
    171         }
    172     }
    173 
    174     public void recordSplitWords() {
    175         mSplitWordsCount++;
    176     }
    177 
    178     public void recordGestureInput(final int numCharsEntered, final long time) {
    179         mGesturesInputCount++;
    180         mGesturesCharsCount += numCharsEntered;
    181         recordUserAction(time, false /* isDeletion */);
    182     }
    183 
    184     public void setIsEmptyUponStarting(final boolean isEmpty) {
    185         mIsEmptyUponStarting = isEmpty;
    186         mIsEmptinessStateKnown = true;
    187     }
    188 
    189     public void recordGestureDelete(final int length, final long time) {
    190         mGesturesDeletedCount++;
    191         recordUserAction(time, true /* isDeletion */);
    192     }
    193 
    194     public void recordManualSuggestion(final long time) {
    195         mManualSuggestionsCount++;
    196         recordUserAction(time, false /* isDeletion */);
    197     }
    198 
    199     public void recordAutoCorrection(final long time) {
    200         mAutoCorrectionsCount++;
    201         recordUserAction(time, false /* isDeletion */);
    202     }
    203 
    204     public void recordRevertCommit(final long time) {
    205         mRevertCommitsCount++;
    206         recordUserAction(time, true /* isDeletion */);
    207     }
    208 
    209     private void recordUserAction(final long time, final boolean isDeletion) {
    210         final long delta = time - mLastTapTime;
    211         if (isDeletion) {
    212             if (delta < MIN_DELETION_INTERMISSION) {
    213                 if (mIsLastKeyDeleteKey) {
    214                     mDuringRepeatedDeleteKeysCounter.add(delta);
    215                 } else {
    216                     mBeforeDeleteKeyCounter.add(delta);
    217                 }
    218             } else {
    219                 ResearchLogger.onUserPause(delta);
    220             }
    221         } else {
    222             if (mIsLastKeyDeleteKey && delta < MIN_DELETION_INTERMISSION) {
    223                 mAfterDeleteKeyCounter.add(delta);
    224             } else if (!mIsLastKeyDeleteKey && delta < MIN_TYPING_INTERMISSION) {
    225                 mKeyCounter.add(delta);
    226             } else {
    227                 ResearchLogger.onUserPause(delta);
    228             }
    229         }
    230         mIsLastKeyDeleteKey = isDeletion;
    231         mLastTapTime = time;
    232     }
    233 }
    234