1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.intelligence.suggestions.ranking; 18 19 import android.content.Context; 20 import android.service.settings.suggestions.Suggestion; 21 import android.support.annotation.VisibleForTesting; 22 23 import com.android.settings.intelligence.overlay.FeatureFactory; 24 25 import java.util.ArrayList; 26 import java.util.Collections; 27 import java.util.Comparator; 28 import java.util.HashMap; 29 import java.util.List; 30 import java.util.Map; 31 32 /** 33 * Copied from packages/apps/Settings/src/.../dashboard/suggestions/SuggestionRanker 34 */ 35 public class SuggestionRanker { 36 37 private static final String TAG = "SuggestionRanker"; 38 39 // The following coefficients form a linear model, which mixes the features to obtain a 40 // relevance metric for ranking the suggestion items. This model is learned with off-line data 41 // by training a binary classifier to detect the clicked items. The higher the obtained 42 // relevance metric, the higher chance of getting clicked. 43 private static final Map<String, Double> WEIGHTS = new HashMap<String, Double>() {{ 44 put(SuggestionFeaturizer.FEATURE_IS_SHOWN, 5.05140842519); 45 put(SuggestionFeaturizer.FEATURE_IS_DISMISSED, 2.29641455171); 46 put(SuggestionFeaturizer.FEATURE_IS_CLICKED, -2.98812233623); 47 put(SuggestionFeaturizer.FEATURE_TIME_FROM_LAST_SHOWN, 5.02807250202); 48 put(SuggestionFeaturizer.FEATURE_TIME_FROM_LAST_DISMISSED, 2.49589700842); 49 put(SuggestionFeaturizer.FEATURE_TIME_FROM_LAST_CLICKED, -4.3377039948); 50 put(SuggestionFeaturizer.FEATURE_SHOWN_COUNT, -2.35993512546); 51 }}; 52 53 private final long mMaxSuggestionsDisplayCount; 54 private final SuggestionFeaturizer mSuggestionFeaturizer; 55 private final Map<Suggestion, Double> mRelevanceMetrics; 56 57 Comparator<Suggestion> suggestionComparator = new Comparator<Suggestion>() { 58 @Override 59 public int compare(Suggestion suggestion1, Suggestion suggestion2) { 60 return mRelevanceMetrics.get(suggestion1) < mRelevanceMetrics.get(suggestion2) ? 1 : -1; 61 } 62 }; 63 64 public SuggestionRanker(Context context, SuggestionFeaturizer suggestionFeaturizer) { 65 mSuggestionFeaturizer = suggestionFeaturizer; 66 mRelevanceMetrics = new HashMap<>(); 67 mMaxSuggestionsDisplayCount = FeatureFactory.get(context).experimentFeatureProvider() 68 .getMaxSuggestionDisplayCount(context); 69 } 70 71 /** 72 * Filter out suggestions that are not relevant at the moment, and rank the rest. 73 * 74 * @return a list of suggestion ranked by relevance. 75 */ 76 public List<Suggestion> rankRelevantSuggestions(List<Suggestion> suggestions) { 77 mRelevanceMetrics.clear(); 78 Map<String, Map<String, Double>> features = mSuggestionFeaturizer.featurize(suggestions); 79 for (Suggestion suggestion : suggestions) { 80 mRelevanceMetrics.put(suggestion, getRelevanceMetric(features.get(suggestion.getId()))); 81 } 82 final List<Suggestion> rankedSuggestions = new ArrayList<>(); 83 rankedSuggestions.addAll(suggestions); 84 Collections.sort(rankedSuggestions, suggestionComparator); 85 86 if (rankedSuggestions.size() < mMaxSuggestionsDisplayCount) { 87 return rankedSuggestions; 88 } else { 89 final List<Suggestion> relevantSuggestions = new ArrayList<>(); 90 for (int i = 0; i < mMaxSuggestionsDisplayCount; i++) { 91 relevantSuggestions.add(rankedSuggestions.get(i)); 92 } 93 return relevantSuggestions; 94 } 95 } 96 97 @VisibleForTesting 98 double getRelevanceMetric(Map<String, Double> features) { 99 double sum = 0; 100 if (features == null) { 101 return sum; 102 } 103 for (String feature : WEIGHTS.keySet()) { 104 sum += WEIGHTS.get(feature) * features.get(feature); 105 } 106 return sum; 107 } 108 }