1 /* 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 15 package com.android.settingslib.core.instrumentation; 16 17 import android.annotation.Nullable; 18 import android.content.ComponentName; 19 import android.content.Context; 20 import android.content.SharedPreferences; 21 import android.content.pm.PackageManager; 22 import android.os.AsyncTask; 23 import android.support.annotation.VisibleForTesting; 24 import android.text.TextUtils; 25 import android.util.Log; 26 import android.util.Pair; 27 28 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 29 30 import java.util.Map; 31 import java.util.Set; 32 import java.util.concurrent.ConcurrentSkipListSet; 33 34 public 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 } 260