ResearchLogger to track simple statistics

Bug: 6188932
Change-Id: Ie1bb7322706c2d4a56f5e17044bc746e9df1cf18
main
Kurt Partridge 2012-07-30 21:55:42 -07:00
parent 94e7f4bef9
commit 0a30688080
2 changed files with 202 additions and 1 deletions

View File

@ -34,6 +34,7 @@ import android.graphics.Paint.Style;
import android.inputmethodservice.InputMethodService;
import android.os.Build;
import android.os.IBinder;
import android.os.SystemClock;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Log;
@ -138,10 +139,12 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
private Dictionary mDictionary;
private KeyboardSwitcher mKeyboardSwitcher;
private InputMethodService mInputMethodService;
private final Statistics mStatistics;
private ResearchLogUploader mResearchLogUploader;
private ResearchLogger() {
mStatistics = Statistics.getInstance();
}
public static ResearchLogger getInstance() {
@ -271,10 +274,35 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
return new File(filesDir, sb.toString());
}
private void checkForEmptyEditor() {
if (mInputMethodService == null) {
return;
}
final InputConnection ic = mInputMethodService.getCurrentInputConnection();
if (ic == null) {
return;
}
final CharSequence textBefore = ic.getTextBeforeCursor(1, 0);
if (!TextUtils.isEmpty(textBefore)) {
mStatistics.setIsEmptyUponStarting(false);
return;
}
final CharSequence textAfter = ic.getTextAfterCursor(1, 0);
if (!TextUtils.isEmpty(textAfter)) {
mStatistics.setIsEmptyUponStarting(false);
return;
}
if (textBefore != null && textAfter != null) {
mStatistics.setIsEmptyUponStarting(true);
}
}
private void start() {
maybeShowSplashScreen();
updateSuspendedState();
requestIndicatorRedraw();
mStatistics.reset();
checkForEmptyEditor();
if (!isAllowedToLog()) {
// Log.w(TAG, "not in usability mode; not logging");
return;
@ -298,6 +326,10 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
}
/* package */ void stop() {
logStatistics();
publishLogUnit(mCurrentLogUnit, true);
mCurrentLogUnit = new LogUnit();
if (mMainResearchLog != null) {
mMainResearchLog.stop();
}
@ -306,6 +338,26 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
}
}
private static final String[] EVENTKEYS_STATISTICS = {
"Statistics", "charCount", "letterCount", "numberCount", "spaceCount", "deleteOpsCount",
"wordCount", "isEmptyUponStarting", "isEmptinessStateKnown", "averageTimeBetweenKeys",
"averageTimeBeforeDelete", "averageTimeDuringRepeatedDelete", "averageTimeAfterDelete"
};
private static void logStatistics() {
final ResearchLogger researchLogger = getInstance();
final Statistics statistics = researchLogger.mStatistics;
final Object[] values = {
statistics.mCharCount, statistics.mLetterCount, statistics.mNumberCount,
statistics.mSpaceCount, statistics.mDeleteKeyCount,
statistics.mWordCount, statistics.mIsEmptyUponStarting,
statistics.mIsEmptinessStateKnown, statistics.mKeyCounter.getAverageTime(),
statistics.mBeforeDeleteKeyCounter.getAverageTime(),
statistics.mDuringRepeatedDeleteKeysCounter.getAverageTime(),
statistics.mAfterDeleteKeyCounter.getAverageTime()
};
researchLogger.enqueueEvent(EVENTKEYS_STATISTICS, values);
}
private void setLoggingAllowed(boolean enableLogging) {
if (mPrefs == null) {
return;
@ -706,6 +758,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
mLoggingFrequencyState.onWordLogged();
}
mCurrentLogUnit = new LogUnit();
mStatistics.recordWordEntered();
}
private void publishLogUnit(LogUnit logUnit, boolean isPrivacySensitive) {
@ -900,7 +953,9 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
final Object[] values = {
Keyboard.printableCode(scrubDigitFromCodePoint(code)), x, y
};
getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_ONCODEINPUT, values);
final ResearchLogger researchLogger = getInstance();
researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_ONCODEINPUT, values);
researchLogger.mStatistics.recordChar(code, SystemClock.uptimeMillis());
}
private static final String[] EVENTKEYS_LATINIME_ONDISPLAYCOMPLETIONS = {

View File

@ -0,0 +1,146 @@
/*
* 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.research;
import com.android.inputmethod.keyboard.Keyboard;
public class Statistics {
// Number of characters entered during a typing session
int mCharCount;
// Number of letter characters entered during a typing session
int mLetterCount;
// Number of number characters entered
int mNumberCount;
// Number of space characters entered
int mSpaceCount;
// Number of delete operations entered (taps on the backspace key)
int mDeleteKeyCount;
// Number of words entered during a session.
int mWordCount;
// Whether the text field was empty upon editing
boolean mIsEmptyUponStarting;
boolean mIsEmptinessStateKnown;
// Timers to count average time to enter a key, first press a delete key,
// between delete keys, and then to return typing after a delete key.
final AverageTimeCounter mKeyCounter = new AverageTimeCounter();
final AverageTimeCounter mBeforeDeleteKeyCounter = new AverageTimeCounter();
final AverageTimeCounter mDuringRepeatedDeleteKeysCounter = new AverageTimeCounter();
final AverageTimeCounter mAfterDeleteKeyCounter = new AverageTimeCounter();
static class AverageTimeCounter {
int mCount;
int mTotalTime;
public void reset() {
mCount = 0;
mTotalTime = 0;
}
public void add(long deltaTime) {
mCount++;
mTotalTime += deltaTime;
}
public int getAverageTime() {
if (mCount == 0) {
return 0;
}
return mTotalTime / mCount;
}
}
// To account for the interruptions when the user's attention is directed elsewhere, times
// longer than MIN_TYPING_INTERMISSION are not counted when estimating this statistic.
public static final int MIN_TYPING_INTERMISSION = 5 * 1000; // in milliseconds
public static final int MIN_DELETION_INTERMISSION = 15 * 1000; // in milliseconds
// The last time that a tap was performed
private long mLastTapTime;
// The type of the last keypress (delete key or not)
boolean mIsLastKeyDeleteKey;
private static final Statistics sInstance = new Statistics();
public static Statistics getInstance() {
return sInstance;
}
private Statistics() {
reset();
}
public void reset() {
mCharCount = 0;
mLetterCount = 0;
mNumberCount = 0;
mSpaceCount = 0;
mDeleteKeyCount = 0;
mWordCount = 0;
mIsEmptyUponStarting = true;
mIsEmptinessStateKnown = false;
mKeyCounter.reset();
mBeforeDeleteKeyCounter.reset();
mDuringRepeatedDeleteKeysCounter.reset();
mAfterDeleteKeyCounter.reset();
mLastTapTime = 0;
mIsLastKeyDeleteKey = false;
}
public void recordChar(int codePoint, long time) {
final long delta = time - mLastTapTime;
if (codePoint == Keyboard.CODE_DELETE) {
mDeleteKeyCount++;
if (delta < MIN_DELETION_INTERMISSION) {
if (mIsLastKeyDeleteKey) {
mDuringRepeatedDeleteKeysCounter.add(delta);
} else {
mBeforeDeleteKeyCounter.add(delta);
}
}
mIsLastKeyDeleteKey = true;
} else {
mCharCount++;
if (Character.isDigit(codePoint)) {
mNumberCount++;
}
if (Character.isLetter(codePoint)) {
mLetterCount++;
}
if (Character.isSpaceChar(codePoint)) {
mSpaceCount++;
}
if (mIsLastKeyDeleteKey && delta < MIN_DELETION_INTERMISSION) {
mAfterDeleteKeyCounter.add(delta);
} else if (!mIsLastKeyDeleteKey && delta < MIN_TYPING_INTERMISSION) {
mKeyCounter.add(delta);
}
mIsLastKeyDeleteKey = false;
}
mLastTapTime = time;
}
public void recordWordEntered() {
mWordCount++;
}
public void setIsEmptyUponStarting(final boolean isEmpty) {
mIsEmptyUponStarting = isEmpty;
mIsEmptinessStateKnown = true;
}
}