1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
|
/*
* Copyright (C) 2013 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 android.content.SharedPreferences;
import android.util.JsonWriter;
import android.util.Log;
import android.view.MotionEvent;
import android.view.inputmethod.CompletionInfo;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.define.ProductionFlag;
import java.io.IOException;
/**
* A template for typed information stored in the logs.
*
* A LogStatement contains a name, keys, and flags about whether the {@code Object[] values}
* associated with the {@code String[] keys} are likely to reveal information about the user. The
* actual values are stored separately.
*/
public class LogStatement {
private static final String TAG = LogStatement.class.getSimpleName();
private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG;
// Constants for particular statements
public static final String TYPE_POINTER_TRACKER_CALL_LISTENER_ON_CODE_INPUT =
"PointerTrackerCallListenerOnCodeInput";
public static final String KEY_CODE = "code";
public static final String VALUE_RESEARCH = "research";
public static final String TYPE_MAIN_KEYBOARD_VIEW_ON_LONG_PRESS =
"MainKeyboardViewOnLongPress";
public static final String ACTION = "action";
public static final String VALUE_DOWN = "DOWN";
public static final String TYPE_MOTION_EVENT = "MotionEvent";
public static final String KEY_IS_LOGGING_RELATED = "isLoggingRelated";
// Keys for internal key/value pairs
private static final String CURRENT_TIME_KEY = "_ct";
private static final String UPTIME_KEY = "_ut";
private static final String EVENT_TYPE_KEY = "_ty";
// Name specifying the LogStatement type.
private final String mType;
// mIsPotentiallyPrivate indicates that event contains potentially private information. If
// the word that this event is a part of is determined to be privacy-sensitive, then this
// event should not be included in the output log. The system waits to output until the
// containing word is known.
private final boolean mIsPotentiallyPrivate;
// mIsPotentiallyRevealing indicates that this statement may disclose details about other
// words typed in other LogUnits. This can happen if the user is not inserting spaces, and
// data from Suggestions and/or Composing text reveals the entire "megaword". For example,
// say the user is typing "for the win", and the system wants to record the bigram "the
// win". If the user types "forthe", omitting the space, the system will give "for the" as
// a suggestion. If the user accepts the autocorrection, the suggestion for "for the" is
// included in the log for the word "the", disclosing that the previous word had been "for".
// For now, we simply do not include this data when logging part of a "megaword".
private final boolean mIsPotentiallyRevealing;
// mKeys stores the names that are the attributes in the output json objects
private final String[] mKeys;
private static final String[] NULL_KEYS = new String[0];
LogStatement(final String name, final boolean isPotentiallyPrivate,
final boolean isPotentiallyRevealing, final String... keys) {
mType = name;
mIsPotentiallyPrivate = isPotentiallyPrivate;
mIsPotentiallyRevealing = isPotentiallyRevealing;
mKeys = (keys == null) ? NULL_KEYS : keys;
}
public String getType() {
return mType;
}
public boolean isPotentiallyPrivate() {
return mIsPotentiallyPrivate;
}
public boolean isPotentiallyRevealing() {
return mIsPotentiallyRevealing;
}
public String[] getKeys() {
return mKeys;
}
/**
* Utility function to test whether a key-value pair exists in a LogStatement.
*
* A LogStatement is really just a template -- it does not contain the values, only the
* keys. So the values must be passed in as an argument.
*
* @param queryKey the String that is tested by {@code String.equals()} to the keys in the
* LogStatement
* @param queryValue an Object that must be {@code Object.equals()} to the key's corresponding
* value in the {@code values} array
* @param values the values corresponding to mKeys
*
* @returns {@true} if {@code queryKey} exists in the keys for this LogStatement, and {@code
* queryValue} matches the corresponding value in {@code values}
*
* @throws IllegalArgumentException if {@code values.length} is not equal to keys().length()
*/
public boolean containsKeyValuePair(final String queryKey, final Object queryValue,
final Object[] values) {
if (mKeys.length != values.length) {
throw new IllegalArgumentException("Mismatched number of keys and values.");
}
final int length = mKeys.length;
for (int i = 0; i < length; i++) {
if (mKeys[i].equals(queryKey) && values[i].equals(queryValue)) {
return true;
}
}
return false;
}
/**
* Utility function to set a value in a LogStatement.
*
* A LogStatement is really just a template -- it does not contain the values, only the
* keys. So the values must be passed in as an argument.
*
* @param queryKey the String that is tested by {@code String.equals()} to the keys in the
* LogStatement
* @param values the array of values corresponding to mKeys
* @param newValue the replacement value to go into the {@code values} array
*
* @returns {@true} if the key exists and the value was successfully set, {@false} otherwise
*
* @throws IllegalArgumentException if {@code values.length} is not equal to keys().length()
*/
public boolean setValue(final String queryKey, final Object[] values, final Object newValue) {
if (mKeys.length != values.length) {
throw new IllegalArgumentException("Mismatched number of keys and values.");
}
final int length = mKeys.length;
for (int i = 0; i < length; i++) {
if (mKeys[i].equals(queryKey)) {
values[i] = newValue;
return true;
}
}
return false;
}
/**
* Write the contents out through jsonWriter.
*
* The JsonWriter class must have already had {@code JsonWriter.beginArray} called on it.
*
* Note that this method is not thread safe for the same jsonWriter. Callers must ensure
* thread safety.
*/
public boolean outputToLocked(final JsonWriter jsonWriter, final Long time,
final Object... values) {
if (DEBUG) {
if (mKeys.length != values.length) {
Log.d(TAG, "Key and Value list sizes do not match. " + mType);
}
}
try {
jsonWriter.beginObject();
jsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis());
jsonWriter.name(UPTIME_KEY).value(time);
jsonWriter.name(EVENT_TYPE_KEY).value(mType);
final int length = values.length;
for (int i = 0; i < length; i++) {
jsonWriter.name(mKeys[i]);
final Object value = values[i];
if (value instanceof CharSequence) {
jsonWriter.value(value.toString());
} else if (value instanceof Number) {
jsonWriter.value((Number) value);
} else if (value instanceof Boolean) {
jsonWriter.value((Boolean) value);
} else if (value instanceof CompletionInfo[]) {
JsonUtils.writeJson((CompletionInfo[]) value, jsonWriter);
} else if (value instanceof SharedPreferences) {
JsonUtils.writeJson((SharedPreferences) value, jsonWriter);
} else if (value instanceof Key[]) {
JsonUtils.writeJson((Key[]) value, jsonWriter);
} else if (value instanceof SuggestedWords) {
JsonUtils.writeJson((SuggestedWords) value, jsonWriter);
} else if (value instanceof MotionEvent) {
JsonUtils.writeJson((MotionEvent) value, jsonWriter);
} else if (value == null) {
jsonWriter.nullValue();
} else {
if (DEBUG) {
Log.w(TAG, "Unrecognized type to be logged: "
+ (value == null ? "<null>" : value.getClass().getName()));
}
jsonWriter.nullValue();
}
}
jsonWriter.endObject();
} catch (IOException e) {
e.printStackTrace();
Log.w(TAG, "Error in JsonWriter; skipping LogStatement");
return false;
}
return true;
}
}
|