Something went wrong on our end
-
Kurt Partridge authored
LogUnits have been annotated with the autocorrected words, but until now this was assumed to be a single word without spaces. But spaceless typing can result in spaces in the LogUnit label. With this change, the LogUnit inspects the autocorrected text to determine how many words were inserted, and counts them accurately. This change corrects a privacy problem, which was that if the word sampling algorithm chose a LogUnit that actually contained multiple words, then more than two successive words would be included in the log. Change-Id: I7c01c3dd3ac33d7e96c00836256bae9c14b124ed
Kurt Partridge authoredLogUnits have been annotated with the autocorrected words, but until now this was assumed to be a single word without spaces. But spaceless typing can result in spaces in the LogUnit label. With this change, the LogUnit inspects the autocorrected text to determine how many words were inserted, and counts them accurately. This change corrects a privacy problem, which was that if the word sampling algorithm chose a LogUnit that actually contained multiple words, then more than two successive words would be included in the log. Change-Id: I7c01c3dd3ac33d7e96c00836256bae9c14b124ed
FixedLogBuffer.java 6.64 KiB
/*
* 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 java.util.ArrayList;
import java.util.LinkedList;
/**
* A buffer that holds a fixed number of LogUnits.
*
* LogUnits are added in and shifted out in temporal order. Only a subset of the LogUnits are
* actual words; the other LogUnits do not count toward the word limit. Once the buffer reaches
* capacity, adding another LogUnit that is a word evicts the oldest LogUnits out one at a time to
* stay under the capacity limit.
*
* This variant of a LogBuffer has a limited memory footprint because of its limited size. This
* makes it useful, for example, for recording a window of the user's most recent actions in case
* they want to report an observed error that they do not know how to reproduce.
*/
public class FixedLogBuffer extends LogBuffer {
/* package for test */ int mWordCapacity;
// The number of members of mLogUnits that are actual words.
private int mNumActualWords;
/**
* Create a new LogBuffer that can hold a fixed number of LogUnits that are words (and
* unlimited number of non-word LogUnits), and that outputs its result to a researchLog.
*
* @param wordCapacity maximum number of words
*/
public FixedLogBuffer(final int wordCapacity) {
super();
if (wordCapacity <= 0) {
throw new IllegalArgumentException("wordCapacity must be 1 or greater.");
}
mWordCapacity = wordCapacity;
mNumActualWords = 0;
}
/**
* Adds a new LogUnit to the front of the LIFO queue, evicting existing LogUnit's
* (oldest first) if word capacity is reached.
*/
@Override
public void shiftIn(final LogUnit newLogUnit) {
if (!newLogUnit.hasOneOrMoreWords()) {
// This LogUnit doesn't contain any word, so it doesn't count toward the word-limit.
super.shiftIn(newLogUnit);
return;
}
final int numWordsIncoming = newLogUnit.getNumWords();
if (mNumActualWords >= mWordCapacity) {
// Give subclass a chance to handle the buffer full condition by shifting out logUnits.
onBufferFull();
// If still full, evict.
if (mNumActualWords >= mWordCapacity) {
shiftOutWords(numWordsIncoming);
}
}
super.shiftIn(newLogUnit);
mNumActualWords += numWordsIncoming;
}
@Override
public LogUnit unshiftIn() {
final LogUnit logUnit = super.unshiftIn();
if (logUnit != null && logUnit.hasOneOrMoreWords()) {
mNumActualWords -= logUnit.getNumWords();
}
return logUnit;
}
public int getNumWords() {
return mNumActualWords;
}
/**
* Removes all LogUnits from the buffer without calling onShiftOut().
*/
@Override
public void clear() {
super.clear();
mNumActualWords = 0;
}
/**
* Called when the buffer has just shifted in one more word than its maximum, and its about to
* shift out LogUnits to bring it back down to the maximum.
*
* Base class does nothing; subclasses may override if they want to record non-privacy sensitive
* events that fall off the end.
*/
protected void onBufferFull() {
}
@Override
public LogUnit shiftOut() {
final LogUnit logUnit = super.shiftOut();
if (logUnit != null && logUnit.hasOneOrMoreWords()) {
mNumActualWords -= logUnit.getNumWords();
}
return logUnit;
}
/**
* Remove LogUnits from the front of the LogBuffer until {@code numWords} have been removed.
*
* If there are less than {@code numWords} word-containing {@link LogUnit}s, shifts out
* all {@code LogUnit}s in the buffer.
*
* @param numWords the minimum number of word-containing {@link LogUnit}s to shift out
* @return the number of actual {@code LogUnit}s shifted out
*/
protected int shiftOutWords(final int numWords) {
int numWordContainingLogUnitsShiftedOut = 0;
for (LogUnit logUnit = shiftOut(); logUnit != null
&& numWordContainingLogUnitsShiftedOut < numWords; logUnit = shiftOut()) {
if (logUnit.hasOneOrMoreWords()) {
numWordContainingLogUnitsShiftedOut += logUnit.getNumWords();
}
}
return numWordContainingLogUnitsShiftedOut;
}
public void shiftOutAll() {
final LinkedList<LogUnit> logUnits = getLogUnits();
while (!logUnits.isEmpty()) {
shiftOut();
}
mNumActualWords = 0;
}
/**
* Returns a list of {@link LogUnit}s at the front of the buffer that have words associated with
* them.
*
* There will be no more than {@code n} words in the returned list. So if 2 words are
* requested, and the first LogUnit has 3 words, it is not returned. If 2 words are requested,
* and the first LogUnit has only 1 word, and the next LogUnit 2 words, only the first LogUnit
* is returned. If the first LogUnit has no words associated with it, and the second LogUnit
* has three words, then only the first LogUnit (which has no associated words) is returned. If
* there are not enough LogUnits in the buffer to meet the word requirement, then all LogUnits
* will be returned.
*
* @param n The maximum number of {@link LogUnit}s with words to return.
* @return The list of the {@link LogUnit}s containing the first n words
*/
public ArrayList<LogUnit> peekAtFirstNWords(int n) {
final LinkedList<LogUnit> logUnits = getLogUnits();
// Allocate space for n*2 logUnits. There will be at least n, one for each word, and
// there may be additional for punctuation, between-word commands, etc. This should be
// enough that reallocation won't be necessary.
final ArrayList<LogUnit> resultList = new ArrayList<LogUnit>(n * 2);
for (final LogUnit logUnit : logUnits) {
n -= logUnit.getNumWords();
if (n < 0) break;
resultList.add(logUnit);
}
return resultList;
}
}