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