Broadscale logging for Settings

Bug: 26687914
Change-Id: Ida75ccf95376538e2ba95d212d333c40fd2dd6e0
diff --git a/src/com/android/settings/InstrumentedFragment.java b/src/com/android/settings/InstrumentedFragment.java
index db14a9cf..b5f99b5 100644
--- a/src/com/android/settings/InstrumentedFragment.java
+++ b/src/com/android/settings/InstrumentedFragment.java
@@ -16,11 +16,11 @@
 
 package com.android.settings;
 
+import com.android.internal.logging.MetricsLogger;
+
 import android.os.Bundle;
 import android.support.v14.preference.PreferenceFragment;
 
-import com.android.internal.logging.MetricsLogger;
-
 /**
  * Instrumented fragment that logs visibility state.
  */
@@ -44,6 +44,10 @@
     public static final int DATA_SAVER_SUMMARY = UNDECLARED + 14;
     public static final int DATA_USAGE_UNRESTRICTED_ACCESS = UNDECLARED + 15;
 
+    // Used for generic logging of Settings Preference Persistence, should not be used
+    // outside SharedPreferencesLogger.
+    public static final int ACTION_GENERIC_PACKAGE = UNDECLARED + 16;
+
     /**
      * Declare the view of this category.
      *
diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java
index ce70f4c..38537fb 100644
--- a/src/com/android/settings/SettingsActivity.java
+++ b/src/com/android/settings/SettingsActivity.java
@@ -457,6 +457,21 @@
         return true;
     }
 
+    @Override
+    public SharedPreferences getSharedPreferences(String name, int mode) {
+        if (name.equals(getPackageName() + "_preferences")) {
+            String tag = getClass().getName();
+            if (getIntent() != null && getIntent().hasExtra(EXTRA_SHOW_FRAGMENT)) {
+                tag = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
+            }
+            if (tag.startsWith("com.android.settings.")) {
+                tag = tag.replace("com.android.settings.", "");
+            }
+            return new SharedPreferencesLogger(this, tag);
+        }
+        return super.getSharedPreferences(name, mode);
+    }
+
     private static boolean isShortCutIntent(final Intent intent) {
         Set<String> categories = intent.getCategories();
         return (categories != null) && categories.contains("com.android.settings.SHORTCUT");
diff --git a/src/com/android/settings/SharedPreferencesLogger.java b/src/com/android/settings/SharedPreferencesLogger.java
new file mode 100644
index 0000000..ae2c4e5
--- /dev/null
+++ b/src/com/android/settings/SharedPreferencesLogger.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2016 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.settings;
+
+import com.android.internal.logging.MetricsLogger;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.os.AsyncTask;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.Map;
+import java.util.Set;
+
+public class SharedPreferencesLogger implements SharedPreferences {
+
+    private final String mTag;
+    private final Context mContext;
+
+    public SharedPreferencesLogger(Context context, String tag) {
+        mContext = context;
+        mTag = tag;
+    }
+
+    @Override
+    public Map<String, ?> getAll() {
+        return null;
+    }
+
+    @Override
+    public String getString(String key, @Nullable String defValue) {
+        return defValue;
+    }
+
+    @Override
+    public Set<String> getStringSet(String key, @Nullable Set<String> defValues) {
+        return defValues;
+    }
+
+    @Override
+    public int getInt(String key, int defValue) {
+        return defValue;
+    }
+
+    @Override
+    public long getLong(String key, long defValue) {
+        return defValue;
+    }
+
+    @Override
+    public float getFloat(String key, float defValue) {
+        return defValue;
+    }
+
+    @Override
+    public boolean getBoolean(String key, boolean defValue) {
+        return defValue;
+    }
+
+    @Override
+    public boolean contains(String key) {
+        return false;
+    }
+
+    @Override
+    public Editor edit() {
+        return new EditorLogger();
+    }
+
+    @Override
+    public void registerOnSharedPreferenceChangeListener(
+            OnSharedPreferenceChangeListener listener) {
+    }
+
+    @Override
+    public void unregisterOnSharedPreferenceChangeListener(
+            OnSharedPreferenceChangeListener listener) {
+    }
+
+    private void logValue(String key, String value) {
+        MetricsLogger.histogram(mContext, mTag + "/" + key + "|" + value, 1);
+    }
+
+    private void logPackageName(String key, String value) {
+        MetricsLogger.histogram(mContext, mTag + "/" + key, 1);
+        MetricsLogger.action(mContext, InstrumentedFragment.ACTION_GENERIC_PACKAGE,
+                mTag + "/" + key + "|" + value);
+    }
+
+    private void safeLogValue(String key, String value) {
+        new AsyncPackageCheck().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, key, value);
+    }
+
+    private class AsyncPackageCheck extends AsyncTask<String, Void, Void> {
+        @Override
+        protected Void doInBackground(String... params) {
+            String key = params[0];
+            String value = params[1];
+            PackageManager pm = mContext.getPackageManager();
+            try {
+                // Check if this might be a component.
+                ComponentName name = ComponentName.unflattenFromString(value);
+                if (value != null) {
+                    value = name.getPackageName();
+                }
+            } catch (Exception e) {
+            }
+            try {
+                pm.getPackageInfo(value, PackageManager.MATCH_UNINSTALLED_PACKAGES);
+                logPackageName(key, value);
+            } catch (PackageManager.NameNotFoundException e) {
+                // Clearly not a package, lets log it.
+                logValue(key, value);
+            }
+            return null;
+        }
+    }
+
+    public class EditorLogger implements Editor {
+        @Override
+        public Editor putString(String key, @Nullable String value) {
+            safeLogValue(key, value);
+            return this;
+        }
+
+        @Override
+        public Editor putStringSet(String key, @Nullable Set<String> values) {
+            safeLogValue(key, TextUtils.join(",", values));
+            return this;
+        }
+
+        @Override
+        public Editor putInt(String key, int value) {
+            logValue(key, String.valueOf(value));
+            return this;
+        }
+
+        @Override
+        public Editor putLong(String key, long value) {
+            logValue(key, String.valueOf(value));
+            return this;
+        }
+
+        @Override
+        public Editor putFloat(String key, float value) {
+            logValue(key, String.valueOf(value));
+            return this;
+        }
+
+        @Override
+        public Editor putBoolean(String key, boolean value) {
+            logValue(key, String.valueOf(value));
+            return this;
+        }
+
+        @Override
+        public Editor remove(String key) {
+            return this;
+        }
+
+        @Override
+        public Editor clear() {
+            return this;
+        }
+
+        @Override
+        public boolean commit() {
+            return true;
+        }
+
+        @Override
+        public void apply() {
+        }
+    }
+}