blob: facce4e0bcbb0673fe892b56ebfdef9c99b6a8da [file] [log] [blame]
Leif Wilden5eb77482018-01-23 23:54:05 +00001/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 * except in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11 * KIND, either express or implied. See the License for the specific language governing
12 * permissions and limitations under the License.
13 */
14
15package com.android.settingslib.core.instrumentation;
16
17import android.annotation.Nullable;
18import android.content.ComponentName;
19import android.content.Context;
20import android.content.SharedPreferences;
21import android.content.pm.PackageManager;
22import android.os.AsyncTask;
23import android.support.annotation.VisibleForTesting;
24import android.text.TextUtils;
25import android.util.Log;
26import android.util.Pair;
27
28import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
29
30import java.util.Map;
31import java.util.Set;
32import java.util.concurrent.ConcurrentSkipListSet;
33
34public class SharedPreferencesLogger implements SharedPreferences {
35
36 private static final String LOG_TAG = "SharedPreferencesLogger";
37
38 private final String mTag;
39 private final Context mContext;
40 private final MetricsFeatureProvider mMetricsFeature;
41 private final Set<String> mPreferenceKeySet;
42
43 public SharedPreferencesLogger(Context context, String tag,
44 MetricsFeatureProvider metricsFeature) {
45 mContext = context;
46 mTag = tag;
47 mMetricsFeature = metricsFeature;
48 mPreferenceKeySet = new ConcurrentSkipListSet<>();
49 }
50
51 @Override
52 public Map<String, ?> getAll() {
53 return null;
54 }
55
56 @Override
57 public String getString(String key, @Nullable String defValue) {
58 return defValue;
59 }
60
61 @Override
62 public Set<String> getStringSet(String key, @Nullable Set<String> defValues) {
63 return defValues;
64 }
65
66 @Override
67 public int getInt(String key, int defValue) {
68 return defValue;
69 }
70
71 @Override
72 public long getLong(String key, long defValue) {
73 return defValue;
74 }
75
76 @Override
77 public float getFloat(String key, float defValue) {
78 return defValue;
79 }
80
81 @Override
82 public boolean getBoolean(String key, boolean defValue) {
83 return defValue;
84 }
85
86 @Override
87 public boolean contains(String key) {
88 return false;
89 }
90
91 @Override
92 public Editor edit() {
93 return new EditorLogger();
94 }
95
96 @Override
97 public void registerOnSharedPreferenceChangeListener(
98 OnSharedPreferenceChangeListener listener) {
99 }
100
101 @Override
102 public void unregisterOnSharedPreferenceChangeListener(
103 OnSharedPreferenceChangeListener listener) {
104 }
105
106 private void logValue(String key, Object value) {
107 logValue(key, value, false /* forceLog */);
108 }
109
110 private void logValue(String key, Object value, boolean forceLog) {
111 final String prefKey = buildPrefKey(mTag, key);
112 if (!forceLog && !mPreferenceKeySet.contains(prefKey)) {
113 // Pref key doesn't exist in set, this is initial display so we skip metrics but
114 // keeps track of this key.
115 mPreferenceKeySet.add(prefKey);
116 return;
117 }
118 // TODO: Remove count logging to save some resource.
119 mMetricsFeature.count(mContext, buildCountName(prefKey, value), 1);
120
121 final Pair<Integer, Object> valueData;
122 if (value instanceof Long) {
123 final Long longVal = (Long) value;
124 final int intVal;
125 if (longVal > Integer.MAX_VALUE) {
126 intVal = Integer.MAX_VALUE;
127 } else if (longVal < Integer.MIN_VALUE) {
128 intVal = Integer.MIN_VALUE;
129 } else {
130 intVal = longVal.intValue();
131 }
132 valueData = Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE,
133 intVal);
134 } else if (value instanceof Integer) {
135 valueData = Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE,
136 value);
137 } else if (value instanceof Boolean) {
138 valueData = Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE,
139 (Boolean) value ? 1 : 0);
140 } else if (value instanceof Float) {
141 valueData = Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_FLOAT_VALUE,
142 value);
143 } else if (value instanceof String) {
144 Log.d(LOG_TAG, "Tried to log string preference " + prefKey + " = " + value);
145 valueData = null;
146 } else {
147 Log.w(LOG_TAG, "Tried to log unloggable object" + value);
148 valueData = null;
149 }
150 if (valueData != null) {
151 // Pref key exists in set, log it's change in metrics.
152 mMetricsFeature.action(mContext, MetricsEvent.ACTION_SETTINGS_PREFERENCE_CHANGE,
153 Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME, prefKey),
154 valueData);
155 }
156 }
157
158 @VisibleForTesting
159 void logPackageName(String key, String value) {
160 final String prefKey = mTag + "/" + key;
161 mMetricsFeature.action(mContext, MetricsEvent.ACTION_SETTINGS_PREFERENCE_CHANGE, value,
162 Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME, prefKey));
163 }
164
165 private void safeLogValue(String key, String value) {
166 new AsyncPackageCheck().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, key, value);
167 }
168
169 public static String buildCountName(String prefKey, Object value) {
170 return prefKey + "|" + value;
171 }
172
173 public static String buildPrefKey(String tag, String key) {
174 return tag + "/" + key;
175 }
176
177 private class AsyncPackageCheck extends AsyncTask<String, Void, Void> {
178 @Override
179 protected Void doInBackground(String... params) {
180 String key = params[0];
181 String value = params[1];
182 PackageManager pm = mContext.getPackageManager();
183 try {
184 // Check if this might be a component.
185 ComponentName name = ComponentName.unflattenFromString(value);
186 if (value != null) {
187 value = name.getPackageName();
188 }
189 } catch (Exception e) {
190 }
191 try {
192 pm.getPackageInfo(value, PackageManager.MATCH_ANY_USER);
193 logPackageName(key, value);
194 } catch (PackageManager.NameNotFoundException e) {
195 // Clearly not a package, and it's unlikely this preference is in prefSet, so
196 // lets force log it.
197 logValue(key, value, true /* forceLog */);
198 }
199 return null;
200 }
201 }
202
203 public class EditorLogger implements Editor {
204 @Override
205 public Editor putString(String key, @Nullable String value) {
206 safeLogValue(key, value);
207 return this;
208 }
209
210 @Override
211 public Editor putStringSet(String key, @Nullable Set<String> values) {
212 safeLogValue(key, TextUtils.join(",", values));
213 return this;
214 }
215
216 @Override
217 public Editor putInt(String key, int value) {
218 logValue(key, value);
219 return this;
220 }
221
222 @Override
223 public Editor putLong(String key, long value) {
224 logValue(key, value);
225 return this;
226 }
227
228 @Override
229 public Editor putFloat(String key, float value) {
230 logValue(key, value);
231 return this;
232 }
233
234 @Override
235 public Editor putBoolean(String key, boolean value) {
236 logValue(key, value);
237 return this;
238 }
239
240 @Override
241 public Editor remove(String key) {
242 return this;
243 }
244
245 @Override
246 public Editor clear() {
247 return this;
248 }
249
250 @Override
251 public boolean commit() {
252 return true;
253 }
254
255 @Override
256 public void apply() {
257 }
258 }
259}