Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3efdec41 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Revert "Move instrumentation classes to SettingsLib to share between mobile/TV.""

parents be982d72 2e978103
Loading
Loading
Loading
Loading
+0 −110
Original line number Diff line number Diff line
/*
 * 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.settingslib.core.instrumentation;

import android.content.Context;
import android.metrics.LogMaker;
import android.util.Log;
import android.util.Pair;

import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;

/**
 * {@link LogWriter} that writes data to eventlog.
 */
public class EventLogWriter implements LogWriter {

    private final MetricsLogger mMetricsLogger = new MetricsLogger();

    public void visible(Context context, int source, int category) {
        final LogMaker logMaker = new LogMaker(category)
                .setType(MetricsProto.MetricsEvent.TYPE_OPEN)
                .addTaggedData(MetricsProto.MetricsEvent.FIELD_CONTEXT, source);
        MetricsLogger.action(logMaker);
    }

    public void hidden(Context context, int category) {
        MetricsLogger.hidden(context, category);
    }

    public void action(int category, int value, Pair<Integer, Object>... taggedData) {
        if (taggedData == null || taggedData.length == 0) {
            mMetricsLogger.action(category, value);
        } else {
            final LogMaker logMaker = new LogMaker(category)
                    .setType(MetricsProto.MetricsEvent.TYPE_ACTION)
                    .setSubtype(value);
            for (Pair<Integer, Object> pair : taggedData) {
                logMaker.addTaggedData(pair.first, pair.second);
            }
            mMetricsLogger.write(logMaker);
        }
    }

    public void action(int category, boolean value, Pair<Integer, Object>... taggedData) {
        action(category, value ? 1 : 0, taggedData);
    }

    public void action(Context context, int category, Pair<Integer, Object>... taggedData) {
        action(context, category, "", taggedData);
    }

    public void actionWithSource(Context context, int source, int category) {
        final LogMaker logMaker = new LogMaker(category)
                .setType(MetricsProto.MetricsEvent.TYPE_ACTION);
        if (source != MetricsProto.MetricsEvent.VIEW_UNKNOWN) {
            logMaker.addTaggedData(MetricsProto.MetricsEvent.FIELD_CONTEXT, source);
        }
        MetricsLogger.action(logMaker);
    }

    /** @deprecated use {@link #action(int, int, Pair[])} */
    @Deprecated
    public void action(Context context, int category, int value) {
        MetricsLogger.action(context, category, value);
    }

    /** @deprecated use {@link #action(int, boolean, Pair[])} */
    @Deprecated
    public void action(Context context, int category, boolean value) {
        MetricsLogger.action(context, category, value);
    }

    public void action(Context context, int category, String pkg,
            Pair<Integer, Object>... taggedData) {
        if (taggedData == null || taggedData.length == 0) {
            MetricsLogger.action(context, category, pkg);
        } else {
            final LogMaker logMaker = new LogMaker(category)
                    .setType(MetricsProto.MetricsEvent.TYPE_ACTION)
                    .setPackageName(pkg);
            for (Pair<Integer, Object> pair : taggedData) {
                logMaker.addTaggedData(pair.first, pair.second);
            }
            MetricsLogger.action(logMaker);
        }
    }

    public void count(Context context, String name, int value) {
        MetricsLogger.count(context, name, value);
    }

    public void histogram(Context context, String name, int bucket) {
        MetricsLogger.histogram(context, name, bucket);
    }
}
+0 −28
Original line number Diff line number Diff line
/*
 * 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.settingslib.core.instrumentation;

public interface Instrumentable {

    int METRICS_CATEGORY_UNKNOWN = 0;

    /**
     * Instrumented name for a view as defined in
     * {@link com.android.internal.logging.nano.MetricsProto.MetricsEvent}.
     */
    int getMetricsCategory();
}
+0 −84
Original line number Diff line number Diff line
/*
 * 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.settingslib.core.instrumentation;

import android.content.Context;
import android.util.Pair;

/**
 * Generic log writer interface.
 */
public interface LogWriter {

    /**
     * Logs a visibility event when view becomes visible.
     */
    void visible(Context context, int source, int category);

    /**
     * Logs a visibility event when view becomes hidden.
     */
    void hidden(Context context, int category);

    /**
     * Logs a user action.
     */
    void action(int category, int value, Pair<Integer, Object>... taggedData);

    /**
     * Logs a user action.
     */
    void action(int category, boolean value, Pair<Integer, Object>... taggedData);

    /**
     * Logs an user action.
     */
    void action(Context context, int category, Pair<Integer, Object>... taggedData);

    /**
     * Logs an user action.
     */
    void actionWithSource(Context context, int source, int category);

    /**
     * Logs an user action.
     * @deprecated use {@link #action(int, int, Pair[])}
     */
    @Deprecated
    void action(Context context, int category, int value);

    /**
     * Logs an user action.
     * @deprecated use {@link #action(int, boolean, Pair[])}
     */
    @Deprecated
    void action(Context context, int category, boolean value);

    /**
     * Logs an user action.
     */
    void action(Context context, int category, String pkg, Pair<Integer, Object>... taggedData);

    /**
     * Logs a count.
     */
    void count(Context context, String name, int value);

    /**
     * Logs a histogram event.
     */
    void histogram(Context context, String name, int bucket);
}
+0 −159
Original line number Diff line number Diff line
/*
 * 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.settingslib.core.instrumentation;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;
import android.util.Pair;

import com.android.internal.logging.nano.MetricsProto.MetricsEvent;

import java.util.ArrayList;
import java.util.List;

/**
 * FeatureProvider for metrics.
 */
public class MetricsFeatureProvider {
    private List<LogWriter> mLoggerWriters;

    public MetricsFeatureProvider() {
        mLoggerWriters = new ArrayList<>();
        installLogWriters();
    }

    protected void installLogWriters() {
        mLoggerWriters.add(new EventLogWriter());
    }

    public void visible(Context context, int source, int category) {
        for (LogWriter writer : mLoggerWriters) {
            writer.visible(context, source, category);
        }
    }

    public void hidden(Context context, int category) {
        for (LogWriter writer : mLoggerWriters) {
            writer.hidden(context, category);
        }
    }

    public void actionWithSource(Context context, int source, int category) {
        for (LogWriter writer : mLoggerWriters) {
            writer.actionWithSource(context, source, category);
        }
    }

    /**
     * Logs a user action. Includes the elapsed time since the containing
     * fragment has been visible.
     */
    public void action(VisibilityLoggerMixin visibilityLogger, int category, int value) {
        for (LogWriter writer : mLoggerWriters) {
            writer.action(category, value,
                    sinceVisibleTaggedData(visibilityLogger.elapsedTimeSinceVisible()));
        }
    }

    /**
     * Logs a user action. Includes the elapsed time since the containing
     * fragment has been visible.
     */
    public void action(VisibilityLoggerMixin visibilityLogger, int category, boolean value) {
        for (LogWriter writer : mLoggerWriters) {
            writer.action(category, value,
                    sinceVisibleTaggedData(visibilityLogger.elapsedTimeSinceVisible()));
        }
    }

    public void action(Context context, int category, Pair<Integer, Object>... taggedData) {
        for (LogWriter writer : mLoggerWriters) {
            writer.action(context, category, taggedData);
        }
    }

    /** @deprecated use {@link #action(VisibilityLoggerMixin, int, int)} */
    @Deprecated
    public void action(Context context, int category, int value) {
        for (LogWriter writer : mLoggerWriters) {
            writer.action(context, category, value);
        }
    }

    /** @deprecated use {@link #action(VisibilityLoggerMixin, int, boolean)} */
    @Deprecated
    public void action(Context context, int category, boolean value) {
        for (LogWriter writer : mLoggerWriters) {
            writer.action(context, category, value);
        }
    }

    public void action(Context context, int category, String pkg,
            Pair<Integer, Object>... taggedData) {
        for (LogWriter writer : mLoggerWriters) {
            writer.action(context, category, pkg, taggedData);
        }
    }

    public void count(Context context, String name, int value) {
        for (LogWriter writer : mLoggerWriters) {
            writer.count(context, name, value);
        }
    }

    public void histogram(Context context, String name, int bucket) {
        for (LogWriter writer : mLoggerWriters) {
            writer.histogram(context, name, bucket);
        }
    }

    public int getMetricsCategory(Object object) {
        if (object == null || !(object instanceof Instrumentable)) {
            return MetricsEvent.VIEW_UNKNOWN;
        }
        return ((Instrumentable) object).getMetricsCategory();
    }

    public void logDashboardStartIntent(Context context, Intent intent,
            int sourceMetricsCategory) {
        if (intent == null) {
            return;
        }
        final ComponentName cn = intent.getComponent();
        if (cn == null) {
            final String action = intent.getAction();
            if (TextUtils.isEmpty(action)) {
                // Not loggable
                return;
            }
            action(context, MetricsEvent.ACTION_SETTINGS_TILE_CLICK, action,
                    Pair.create(MetricsEvent.FIELD_CONTEXT, sourceMetricsCategory));
            return;
        } else if (TextUtils.equals(cn.getPackageName(), context.getPackageName())) {
            // Going to a Setting internal page, skip click logging in favor of page's own
            // visibility logging.
            return;
        }
        action(context, MetricsEvent.ACTION_SETTINGS_TILE_CLICK, cn.flattenToString(),
                Pair.create(MetricsEvent.FIELD_CONTEXT, sourceMetricsCategory));
    }

    private Pair<Integer, Object> sinceVisibleTaggedData(long timestamp) {
        return Pair.create(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS, timestamp);
    }
}
+0 −259
Original line number Diff line number Diff line
/*
 * 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.settingslib.core.instrumentation;

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.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;

import com.android.internal.logging.nano.MetricsProto.MetricsEvent;

import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentSkipListSet;

public class SharedPreferencesLogger implements SharedPreferences {

    private static final String LOG_TAG = "SharedPreferencesLogger";

    private final String mTag;
    private final Context mContext;
    private final MetricsFeatureProvider mMetricsFeature;
    private final Set<String> mPreferenceKeySet;

    public SharedPreferencesLogger(Context context, String tag,
            MetricsFeatureProvider metricsFeature) {
        mContext = context;
        mTag = tag;
        mMetricsFeature = metricsFeature;
        mPreferenceKeySet = new ConcurrentSkipListSet<>();
    }

    @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, Object value) {
        logValue(key, value, false /* forceLog */);
    }

    private void logValue(String key, Object value, boolean forceLog) {
        final String prefKey = buildPrefKey(mTag, key);
        if (!forceLog && !mPreferenceKeySet.contains(prefKey)) {
            // Pref key doesn't exist in set, this is initial display so we skip metrics but
            // keeps track of this key.
            mPreferenceKeySet.add(prefKey);
            return;
        }
        // TODO: Remove count logging to save some resource.
        mMetricsFeature.count(mContext, buildCountName(prefKey, value), 1);

        final Pair<Integer, Object> valueData;
        if (value instanceof Long) {
            final Long longVal = (Long) value;
            final int intVal;
            if (longVal > Integer.MAX_VALUE) {
                intVal = Integer.MAX_VALUE;
            } else if (longVal < Integer.MIN_VALUE) {
                intVal = Integer.MIN_VALUE;
            } else {
                intVal = longVal.intValue();
            }
            valueData = Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE,
                    intVal);
        } else if (value instanceof Integer) {
            valueData = Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE,
                    value);
        } else if (value instanceof Boolean) {
            valueData = Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE,
                    (Boolean) value ? 1 : 0);
        } else if (value instanceof Float) {
            valueData = Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_FLOAT_VALUE,
                    value);
        } else if (value instanceof String) {
            Log.d(LOG_TAG, "Tried to log string preference " + prefKey + " = " + value);
            valueData = null;
        } else {
            Log.w(LOG_TAG, "Tried to log unloggable object" + value);
            valueData = null;
        }
        if (valueData != null) {
            // Pref key exists in set, log it's change in metrics.
            mMetricsFeature.action(mContext, MetricsEvent.ACTION_SETTINGS_PREFERENCE_CHANGE,
                    Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME, prefKey),
                    valueData);
        }
    }

    @VisibleForTesting
    void logPackageName(String key, String value) {
        final String prefKey = mTag + "/" + key;
        mMetricsFeature.action(mContext, MetricsEvent.ACTION_SETTINGS_PREFERENCE_CHANGE, value,
                Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME, prefKey));
    }

    private void safeLogValue(String key, String value) {
        new AsyncPackageCheck().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, key, value);
    }

    public static String buildCountName(String prefKey, Object value) {
        return prefKey + "|" + value;
    }

    public static String buildPrefKey(String tag, String key) {
        return tag + "/" + key;
    }

    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_ANY_USER);
                logPackageName(key, value);
            } catch (PackageManager.NameNotFoundException e) {
                // Clearly not a package, and it's unlikely this preference is in prefSet, so
                // lets force log it.
                logValue(key, value, true /* forceLog */);
            }
            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, value);
            return this;
        }

        @Override
        public Editor putLong(String key, long value) {
            logValue(key, value);
            return this;
        }

        @Override
        public Editor putFloat(String key, float value) {
            logValue(key, value);
            return this;
        }

        @Override
        public Editor putBoolean(String key, boolean value) {
            logValue(key, 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() {
        }
    }
}
Loading