Home | History | Annotate | Download | only in instrumentation
      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