Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0a306880 authored by Kurt Partridge's avatar Kurt Partridge
Browse files

ResearchLogger to track simple statistics

Bug: 6188932
Change-Id: Ie1bb7322706c2d4a56f5e17044bc746e9df1cf18
parent 94e7f4be
Loading
Loading
Loading
Loading
+56 −1
Original line number Diff line number Diff line
@@ -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 = {
+146 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}