Loading src/com/android/settings/intelligence/suggestions/ranking/EventStore.java 0 → 100644 +104 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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.intelligence.suggestions.ranking; import android.content.Context; import android.content.SharedPreferences; import android.util.Log; import java.util.Arrays; import java.util.HashSet; import java.util.Set; /** * Copied from packages/apps/Settings/src/.../dashboard/suggestions/EventStore */ public class EventStore { public static final String TAG = "SuggestionEventStore"; public static final String EVENT_SHOWN = "shown"; public static final String EVENT_DISMISSED = "dismissed"; public static final String EVENT_CLICKED = "clicked"; public static final String METRIC_LAST_EVENT_TIME = "last_event_time"; public static final String METRIC_COUNT = "count"; private static final Set<String> EVENTS = new HashSet<String>( Arrays.asList(new String[]{EVENT_SHOWN, EVENT_DISMISSED, EVENT_CLICKED})); private static final Set<String> METRICS = new HashSet<String>( Arrays.asList(new String[]{METRIC_LAST_EVENT_TIME, METRIC_COUNT})); private final SharedPreferences mSharedPrefs; public EventStore(Context context) { mSharedPrefs = context.getSharedPreferences(TAG, Context.MODE_PRIVATE); } /** * Writes individual log events. * * @param pkgName: Package for which this event is reported. * @param eventType: Type of event (one of {@link #EVENTS}). */ public void writeEvent(String pkgName, String eventType) { if (!EVENTS.contains(eventType)) { Log.w(TAG, "Reported event type " + eventType + " is not a valid type!"); return; } final String lastTimePrefKey = getPrefKey(pkgName, eventType, METRIC_LAST_EVENT_TIME); final String countPrefKey = getPrefKey(pkgName, eventType, METRIC_COUNT); writePref(lastTimePrefKey, System.currentTimeMillis()); writePref(countPrefKey, readPref(countPrefKey, (long) 0) + 1); } /** * Reads metric of the the reported events (e.g., counts). * * @param pkgName: Package for which this metric is queried. * @param eventType: Type of event (one of {@link #EVENTS}). * @param metricType: Type of the queried metric (one of {@link #METRICS}). * @return the corresponding metric. */ public long readMetric(String pkgName, String eventType, String metricType) { if (!EVENTS.contains(eventType)) { Log.w(TAG, "Reported event type " + eventType + " is not a valid event!"); return 0; } else if (!METRICS.contains(metricType)) { Log.w(TAG, "Required stat type + " + metricType + " is not a valid stat!"); return 0; } return readPref(getPrefKey(pkgName, eventType, metricType), (long) 0); } private void writePref(String prefKey, long value) { mSharedPrefs.edit().putLong(prefKey, value).commit(); } private long readPref(String prefKey, Long defaultValue) { return mSharedPrefs.getLong(prefKey, defaultValue); } private String getPrefKey(String pkgName, String eventType, String statType) { return new StringBuilder() .append("setting_suggestion_") .append(pkgName) .append("_") .append(eventType) .append("_") .append(statType) .toString(); } } No newline at end of file src/com/android/settings/intelligence/suggestions/ranking/SuggestionFeaturizer.java 0 → 100644 +114 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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.intelligence.suggestions.ranking; import android.service.settings.suggestions.Suggestion; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Creates a set of interaction features (i.e., metrics) to represent each setting suggestion. These * features currently include normalized time from previous events (shown, dismissed and clicked) * for any particular suggestion and also counts of these events. These features are used as signals * to find the best ranking for suggestion items. * <p/> * Copied from packages/apps/Settings/src/.../dashboard/suggestions/SuggestionFeaturizer */ public class SuggestionFeaturizer { // Key of the features used for ranking. public static final String FEATURE_IS_SHOWN = "is_shown"; public static final String FEATURE_IS_DISMISSED = "is_dismissed"; public static final String FEATURE_IS_CLICKED = "is_clicked"; public static final String FEATURE_TIME_FROM_LAST_SHOWN = "time_from_last_shown"; public static final String FEATURE_TIME_FROM_LAST_DISMISSED = "time_from_last_dismissed"; public static final String FEATURE_TIME_FROM_LAST_CLICKED = "time_from_last_clicked"; public static final String FEATURE_SHOWN_COUNT = "shown_count"; public static final String FEATURE_DISMISSED_COUNT = "dismissed_count"; public static final String FEATURE_CLICKED_COUNT = "clicked_count"; // The following numbers are estimated from histograms. public static final double TIME_NORMALIZATION_FACTOR = 2e10; public static final double COUNT_NORMALIZATION_FACTOR = 500; private final EventStore mEventStore; /** * Constructor * * @param eventStore An instance of {@code EventStore} which maintains the recorded suggestion * events. */ public SuggestionFeaturizer(EventStore eventStore) { mEventStore = eventStore; } /** * Extracts the features for each package name. * * @return A Map containing the features, keyed by the package names. Each map value contains * another map with key-value pairs of the features. */ public Map<String, Map<String, Double>> featurize(List<Suggestion> suggestions) { Map<String, Map<String, Double>> features = new HashMap<>(); Long curTimeMs = System.currentTimeMillis(); List<String> pkgNames = new ArrayList<>(suggestions.size()); for (Suggestion suggestion : suggestions) { pkgNames.add(suggestion.getId()); } for (String pkgName : pkgNames) { Map<String, Double> featureMap = new HashMap<>(); features.put(pkgName, featureMap); Long lastShownTime = mEventStore .readMetric(pkgName, EventStore.EVENT_SHOWN, EventStore.METRIC_LAST_EVENT_TIME); Long lastDismissedTime = mEventStore.readMetric(pkgName, EventStore.EVENT_DISMISSED, EventStore.METRIC_LAST_EVENT_TIME); Long lastClickedTime = mEventStore.readMetric(pkgName, EventStore.EVENT_CLICKED, EventStore.METRIC_LAST_EVENT_TIME); featureMap.put(FEATURE_IS_SHOWN, booleanToDouble(lastShownTime > 0)); featureMap.put(FEATURE_IS_DISMISSED, booleanToDouble(lastDismissedTime > 0)); featureMap.put(FEATURE_IS_CLICKED, booleanToDouble(lastClickedTime > 0)); featureMap.put(FEATURE_TIME_FROM_LAST_SHOWN, normalizedTimeDiff(curTimeMs, lastShownTime)); featureMap.put(FEATURE_TIME_FROM_LAST_DISMISSED, normalizedTimeDiff(curTimeMs, lastDismissedTime)); featureMap.put(FEATURE_TIME_FROM_LAST_CLICKED, normalizedTimeDiff(curTimeMs, lastClickedTime)); featureMap.put(FEATURE_SHOWN_COUNT, normalizedCount(mEventStore .readMetric(pkgName, EventStore.EVENT_SHOWN, EventStore.METRIC_COUNT))); featureMap.put(FEATURE_DISMISSED_COUNT, normalizedCount(mEventStore .readMetric(pkgName, EventStore.EVENT_DISMISSED, EventStore.METRIC_COUNT))); featureMap.put(FEATURE_CLICKED_COUNT, normalizedCount(mEventStore .readMetric(pkgName, EventStore.EVENT_CLICKED, EventStore.METRIC_COUNT))); } return features; } private static double booleanToDouble(boolean bool) { return bool ? 1 : 0; } private static double normalizedTimeDiff(long curTimeMs, long preTimeMs) { return Math.min(1, (curTimeMs - preTimeMs) / TIME_NORMALIZATION_FACTOR); } private static double normalizedCount(long count) { return Math.min(1, count / COUNT_NORMALIZATION_FACTOR); } } No newline at end of file src/com/android/settings/intelligence/suggestions/ranking/SuggestionRanker.java 0 → 100644 +95 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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.intelligence.suggestions.ranking; import android.content.Context; import android.service.settings.suggestions.Suggestion; import android.support.annotation.VisibleForTesting; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Copied from packages/apps/Settings/src/.../dashboard/suggestions/SuggestionRanker */ public class SuggestionRanker { private static final String TAG = "SuggestionRanker"; // The following coefficients form a linear model, which mixes the features to obtain a // relevance metric for ranking the suggestion items. This model is learned with off-line data // by training a binary classifier to detect the clicked items. The higher the obtained // relevance metric, the higher chance of getting clicked. private static final Map<String, Double> WEIGHTS = new HashMap<String, Double>() {{ put(SuggestionFeaturizer.FEATURE_IS_SHOWN, 5.05140842519); put(SuggestionFeaturizer.FEATURE_IS_DISMISSED, 2.29641455171); put(SuggestionFeaturizer.FEATURE_IS_CLICKED, -2.98812233623); put(SuggestionFeaturizer.FEATURE_TIME_FROM_LAST_SHOWN, 5.02807250202); put(SuggestionFeaturizer.FEATURE_TIME_FROM_LAST_DISMISSED, 2.49589700842); put(SuggestionFeaturizer.FEATURE_TIME_FROM_LAST_CLICKED, -4.3377039948); put(SuggestionFeaturizer.FEATURE_SHOWN_COUNT, -2.35993512546); }}; private static SuggestionRanker sInstance; private final SuggestionFeaturizer mSuggestionFeaturizer; private final Map<Suggestion, Double> relevanceMetrics; Comparator<Suggestion> suggestionComparator = new Comparator<Suggestion>() { @Override public int compare(Suggestion suggestion1, Suggestion suggestion2) { return relevanceMetrics.get(suggestion1) < relevanceMetrics.get(suggestion2) ? 1 : -1; } }; @VisibleForTesting SuggestionRanker(SuggestionFeaturizer suggestionFeaturizer) { mSuggestionFeaturizer = suggestionFeaturizer; relevanceMetrics = new HashMap<>(); } public static SuggestionRanker getInstance(Context context) { if (sInstance == null) { sInstance = new SuggestionRanker( new SuggestionFeaturizer(new EventStore(context.getApplicationContext()))); } return sInstance; } public void rankSuggestions(List<Suggestion> suggestions) { relevanceMetrics.clear(); Map<String, Map<String, Double>> features = mSuggestionFeaturizer.featurize(suggestions); for (Suggestion suggestion : suggestions) { relevanceMetrics.put(suggestion, getRelevanceMetric(features.get(suggestion.getId()))); } Collections.sort(suggestions, suggestionComparator); } @VisibleForTesting double getRelevanceMetric(Map<String, Double> features) { double sum = 0; if (features == null) { return sum; } for (String feature : WEIGHTS.keySet()) { sum += WEIGHTS.get(feature) * features.get(feature); } return sum; } } No newline at end of file tests/robotests/Android.mk +1 −0 Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ LOCAL_SRC_FILES := $(call all-java-files-under, runners/android_mk src) # Include the testing libraries (JUnit4 + Robolectric libs). LOCAL_STATIC_JAVA_LIBRARIES := \ mockito-robolectric-prebuilt \ truth-prebuilt LOCAL_JAVA_LIBRARIES := \ Loading tests/robotests/src/android/service/settings/suggestions/Suggestion.java 0 → 100644 +119 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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 android.service.settings.suggestions; import android.app.PendingIntent; import android.os.Parcel; import android.text.TextUtils; public class Suggestion { private final String mId; private final CharSequence mTitle; private final CharSequence mSummary; private final PendingIntent mPendingIntent; /** * Gets the id for the suggestion object. */ public String getId() { return mId; } /** * Title of the suggestion that is shown to the user. */ public CharSequence getTitle() { return mTitle; } /** * Optional summary describing what this suggestion controls. */ public CharSequence getSummary() { return mSummary; } /** * The Intent to launch when the suggestion is activated. */ public PendingIntent getPendingIntent() { return mPendingIntent; } private Suggestion(Builder builder) { mTitle = builder.mTitle; mSummary = builder.mSummary; mPendingIntent = builder.mPendingIntent; mId = builder.mId; } private Suggestion(Parcel in) { mId = in.readString(); mTitle = in.readCharSequence(); mSummary = in.readCharSequence(); mPendingIntent = in.readParcelable(PendingIntent.class.getClassLoader()); } /** * Builder class for {@link Suggestion}. */ public static class Builder { private final String mId; private CharSequence mTitle; private CharSequence mSummary; private PendingIntent mPendingIntent; public Builder(String id) { if (TextUtils.isEmpty(id)) { throw new IllegalArgumentException("Suggestion id cannot be empty"); } mId = id; } /** * Sets suggestion title */ public Builder setTitle(CharSequence title) { mTitle = title; return this; } /** * Sets suggestion summary */ public Builder setSummary(CharSequence summary) { mSummary = summary; return this; } /** * Sets suggestion intent */ public Builder setPendingIntent(PendingIntent pendingIntent) { mPendingIntent = pendingIntent; return this; } /** * Builds an immutable {@link Suggestion} object. */ public Suggestion build() { return new Suggestion(this /* builder */); } } } Loading
src/com/android/settings/intelligence/suggestions/ranking/EventStore.java 0 → 100644 +104 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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.intelligence.suggestions.ranking; import android.content.Context; import android.content.SharedPreferences; import android.util.Log; import java.util.Arrays; import java.util.HashSet; import java.util.Set; /** * Copied from packages/apps/Settings/src/.../dashboard/suggestions/EventStore */ public class EventStore { public static final String TAG = "SuggestionEventStore"; public static final String EVENT_SHOWN = "shown"; public static final String EVENT_DISMISSED = "dismissed"; public static final String EVENT_CLICKED = "clicked"; public static final String METRIC_LAST_EVENT_TIME = "last_event_time"; public static final String METRIC_COUNT = "count"; private static final Set<String> EVENTS = new HashSet<String>( Arrays.asList(new String[]{EVENT_SHOWN, EVENT_DISMISSED, EVENT_CLICKED})); private static final Set<String> METRICS = new HashSet<String>( Arrays.asList(new String[]{METRIC_LAST_EVENT_TIME, METRIC_COUNT})); private final SharedPreferences mSharedPrefs; public EventStore(Context context) { mSharedPrefs = context.getSharedPreferences(TAG, Context.MODE_PRIVATE); } /** * Writes individual log events. * * @param pkgName: Package for which this event is reported. * @param eventType: Type of event (one of {@link #EVENTS}). */ public void writeEvent(String pkgName, String eventType) { if (!EVENTS.contains(eventType)) { Log.w(TAG, "Reported event type " + eventType + " is not a valid type!"); return; } final String lastTimePrefKey = getPrefKey(pkgName, eventType, METRIC_LAST_EVENT_TIME); final String countPrefKey = getPrefKey(pkgName, eventType, METRIC_COUNT); writePref(lastTimePrefKey, System.currentTimeMillis()); writePref(countPrefKey, readPref(countPrefKey, (long) 0) + 1); } /** * Reads metric of the the reported events (e.g., counts). * * @param pkgName: Package for which this metric is queried. * @param eventType: Type of event (one of {@link #EVENTS}). * @param metricType: Type of the queried metric (one of {@link #METRICS}). * @return the corresponding metric. */ public long readMetric(String pkgName, String eventType, String metricType) { if (!EVENTS.contains(eventType)) { Log.w(TAG, "Reported event type " + eventType + " is not a valid event!"); return 0; } else if (!METRICS.contains(metricType)) { Log.w(TAG, "Required stat type + " + metricType + " is not a valid stat!"); return 0; } return readPref(getPrefKey(pkgName, eventType, metricType), (long) 0); } private void writePref(String prefKey, long value) { mSharedPrefs.edit().putLong(prefKey, value).commit(); } private long readPref(String prefKey, Long defaultValue) { return mSharedPrefs.getLong(prefKey, defaultValue); } private String getPrefKey(String pkgName, String eventType, String statType) { return new StringBuilder() .append("setting_suggestion_") .append(pkgName) .append("_") .append(eventType) .append("_") .append(statType) .toString(); } } No newline at end of file
src/com/android/settings/intelligence/suggestions/ranking/SuggestionFeaturizer.java 0 → 100644 +114 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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.intelligence.suggestions.ranking; import android.service.settings.suggestions.Suggestion; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Creates a set of interaction features (i.e., metrics) to represent each setting suggestion. These * features currently include normalized time from previous events (shown, dismissed and clicked) * for any particular suggestion and also counts of these events. These features are used as signals * to find the best ranking for suggestion items. * <p/> * Copied from packages/apps/Settings/src/.../dashboard/suggestions/SuggestionFeaturizer */ public class SuggestionFeaturizer { // Key of the features used for ranking. public static final String FEATURE_IS_SHOWN = "is_shown"; public static final String FEATURE_IS_DISMISSED = "is_dismissed"; public static final String FEATURE_IS_CLICKED = "is_clicked"; public static final String FEATURE_TIME_FROM_LAST_SHOWN = "time_from_last_shown"; public static final String FEATURE_TIME_FROM_LAST_DISMISSED = "time_from_last_dismissed"; public static final String FEATURE_TIME_FROM_LAST_CLICKED = "time_from_last_clicked"; public static final String FEATURE_SHOWN_COUNT = "shown_count"; public static final String FEATURE_DISMISSED_COUNT = "dismissed_count"; public static final String FEATURE_CLICKED_COUNT = "clicked_count"; // The following numbers are estimated from histograms. public static final double TIME_NORMALIZATION_FACTOR = 2e10; public static final double COUNT_NORMALIZATION_FACTOR = 500; private final EventStore mEventStore; /** * Constructor * * @param eventStore An instance of {@code EventStore} which maintains the recorded suggestion * events. */ public SuggestionFeaturizer(EventStore eventStore) { mEventStore = eventStore; } /** * Extracts the features for each package name. * * @return A Map containing the features, keyed by the package names. Each map value contains * another map with key-value pairs of the features. */ public Map<String, Map<String, Double>> featurize(List<Suggestion> suggestions) { Map<String, Map<String, Double>> features = new HashMap<>(); Long curTimeMs = System.currentTimeMillis(); List<String> pkgNames = new ArrayList<>(suggestions.size()); for (Suggestion suggestion : suggestions) { pkgNames.add(suggestion.getId()); } for (String pkgName : pkgNames) { Map<String, Double> featureMap = new HashMap<>(); features.put(pkgName, featureMap); Long lastShownTime = mEventStore .readMetric(pkgName, EventStore.EVENT_SHOWN, EventStore.METRIC_LAST_EVENT_TIME); Long lastDismissedTime = mEventStore.readMetric(pkgName, EventStore.EVENT_DISMISSED, EventStore.METRIC_LAST_EVENT_TIME); Long lastClickedTime = mEventStore.readMetric(pkgName, EventStore.EVENT_CLICKED, EventStore.METRIC_LAST_EVENT_TIME); featureMap.put(FEATURE_IS_SHOWN, booleanToDouble(lastShownTime > 0)); featureMap.put(FEATURE_IS_DISMISSED, booleanToDouble(lastDismissedTime > 0)); featureMap.put(FEATURE_IS_CLICKED, booleanToDouble(lastClickedTime > 0)); featureMap.put(FEATURE_TIME_FROM_LAST_SHOWN, normalizedTimeDiff(curTimeMs, lastShownTime)); featureMap.put(FEATURE_TIME_FROM_LAST_DISMISSED, normalizedTimeDiff(curTimeMs, lastDismissedTime)); featureMap.put(FEATURE_TIME_FROM_LAST_CLICKED, normalizedTimeDiff(curTimeMs, lastClickedTime)); featureMap.put(FEATURE_SHOWN_COUNT, normalizedCount(mEventStore .readMetric(pkgName, EventStore.EVENT_SHOWN, EventStore.METRIC_COUNT))); featureMap.put(FEATURE_DISMISSED_COUNT, normalizedCount(mEventStore .readMetric(pkgName, EventStore.EVENT_DISMISSED, EventStore.METRIC_COUNT))); featureMap.put(FEATURE_CLICKED_COUNT, normalizedCount(mEventStore .readMetric(pkgName, EventStore.EVENT_CLICKED, EventStore.METRIC_COUNT))); } return features; } private static double booleanToDouble(boolean bool) { return bool ? 1 : 0; } private static double normalizedTimeDiff(long curTimeMs, long preTimeMs) { return Math.min(1, (curTimeMs - preTimeMs) / TIME_NORMALIZATION_FACTOR); } private static double normalizedCount(long count) { return Math.min(1, count / COUNT_NORMALIZATION_FACTOR); } } No newline at end of file
src/com/android/settings/intelligence/suggestions/ranking/SuggestionRanker.java 0 → 100644 +95 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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.intelligence.suggestions.ranking; import android.content.Context; import android.service.settings.suggestions.Suggestion; import android.support.annotation.VisibleForTesting; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Copied from packages/apps/Settings/src/.../dashboard/suggestions/SuggestionRanker */ public class SuggestionRanker { private static final String TAG = "SuggestionRanker"; // The following coefficients form a linear model, which mixes the features to obtain a // relevance metric for ranking the suggestion items. This model is learned with off-line data // by training a binary classifier to detect the clicked items. The higher the obtained // relevance metric, the higher chance of getting clicked. private static final Map<String, Double> WEIGHTS = new HashMap<String, Double>() {{ put(SuggestionFeaturizer.FEATURE_IS_SHOWN, 5.05140842519); put(SuggestionFeaturizer.FEATURE_IS_DISMISSED, 2.29641455171); put(SuggestionFeaturizer.FEATURE_IS_CLICKED, -2.98812233623); put(SuggestionFeaturizer.FEATURE_TIME_FROM_LAST_SHOWN, 5.02807250202); put(SuggestionFeaturizer.FEATURE_TIME_FROM_LAST_DISMISSED, 2.49589700842); put(SuggestionFeaturizer.FEATURE_TIME_FROM_LAST_CLICKED, -4.3377039948); put(SuggestionFeaturizer.FEATURE_SHOWN_COUNT, -2.35993512546); }}; private static SuggestionRanker sInstance; private final SuggestionFeaturizer mSuggestionFeaturizer; private final Map<Suggestion, Double> relevanceMetrics; Comparator<Suggestion> suggestionComparator = new Comparator<Suggestion>() { @Override public int compare(Suggestion suggestion1, Suggestion suggestion2) { return relevanceMetrics.get(suggestion1) < relevanceMetrics.get(suggestion2) ? 1 : -1; } }; @VisibleForTesting SuggestionRanker(SuggestionFeaturizer suggestionFeaturizer) { mSuggestionFeaturizer = suggestionFeaturizer; relevanceMetrics = new HashMap<>(); } public static SuggestionRanker getInstance(Context context) { if (sInstance == null) { sInstance = new SuggestionRanker( new SuggestionFeaturizer(new EventStore(context.getApplicationContext()))); } return sInstance; } public void rankSuggestions(List<Suggestion> suggestions) { relevanceMetrics.clear(); Map<String, Map<String, Double>> features = mSuggestionFeaturizer.featurize(suggestions); for (Suggestion suggestion : suggestions) { relevanceMetrics.put(suggestion, getRelevanceMetric(features.get(suggestion.getId()))); } Collections.sort(suggestions, suggestionComparator); } @VisibleForTesting double getRelevanceMetric(Map<String, Double> features) { double sum = 0; if (features == null) { return sum; } for (String feature : WEIGHTS.keySet()) { sum += WEIGHTS.get(feature) * features.get(feature); } return sum; } } No newline at end of file
tests/robotests/Android.mk +1 −0 Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ LOCAL_SRC_FILES := $(call all-java-files-under, runners/android_mk src) # Include the testing libraries (JUnit4 + Robolectric libs). LOCAL_STATIC_JAVA_LIBRARIES := \ mockito-robolectric-prebuilt \ truth-prebuilt LOCAL_JAVA_LIBRARIES := \ Loading
tests/robotests/src/android/service/settings/suggestions/Suggestion.java 0 → 100644 +119 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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 android.service.settings.suggestions; import android.app.PendingIntent; import android.os.Parcel; import android.text.TextUtils; public class Suggestion { private final String mId; private final CharSequence mTitle; private final CharSequence mSummary; private final PendingIntent mPendingIntent; /** * Gets the id for the suggestion object. */ public String getId() { return mId; } /** * Title of the suggestion that is shown to the user. */ public CharSequence getTitle() { return mTitle; } /** * Optional summary describing what this suggestion controls. */ public CharSequence getSummary() { return mSummary; } /** * The Intent to launch when the suggestion is activated. */ public PendingIntent getPendingIntent() { return mPendingIntent; } private Suggestion(Builder builder) { mTitle = builder.mTitle; mSummary = builder.mSummary; mPendingIntent = builder.mPendingIntent; mId = builder.mId; } private Suggestion(Parcel in) { mId = in.readString(); mTitle = in.readCharSequence(); mSummary = in.readCharSequence(); mPendingIntent = in.readParcelable(PendingIntent.class.getClassLoader()); } /** * Builder class for {@link Suggestion}. */ public static class Builder { private final String mId; private CharSequence mTitle; private CharSequence mSummary; private PendingIntent mPendingIntent; public Builder(String id) { if (TextUtils.isEmpty(id)) { throw new IllegalArgumentException("Suggestion id cannot be empty"); } mId = id; } /** * Sets suggestion title */ public Builder setTitle(CharSequence title) { mTitle = title; return this; } /** * Sets suggestion summary */ public Builder setSummary(CharSequence summary) { mSummary = summary; return this; } /** * Sets suggestion intent */ public Builder setPendingIntent(PendingIntent pendingIntent) { mPendingIntent = pendingIntent; return this; } /** * Builds an immutable {@link Suggestion} object. */ public Suggestion build() { return new Suggestion(this /* builder */); } } }