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 androidx.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 = Map.of( 44 SuggestionFeaturizer.FEATURE_IS_SHOWN, 5.05140842519, 45 SuggestionFeaturizer.FEATURE_IS_DISMISSED, 2.29641455171, 46 SuggestionFeaturizer.FEATURE_IS_CLICKED, -2.98812233623, 47 SuggestionFeaturizer.FEATURE_TIME_FROM_LAST_SHOWN, 5.02807250202, 48 SuggestionFeaturizer.FEATURE_TIME_FROM_LAST_DISMISSED, 2.49589700842, 49 SuggestionFeaturizer.FEATURE_TIME_FROM_LAST_CLICKED, -4.3377039948, 50 SuggestionFeaturizer.FEATURE_SHOWN_COUNT, -2.35993512546); 51 52 private final long mMaxSuggestionsDisplayCount; 53 private final SuggestionFeaturizer mSuggestionFeaturizer; 54 private final Map<Suggestion, Double> mRelevanceMetrics; 55 56 Comparator<Suggestion> suggestionComparator = new Comparator<Suggestion>() { 57 @Override 58 public int compare(Suggestion suggestion1, Suggestion suggestion2) { 59 return mRelevanceMetrics.get(suggestion1) < mRelevanceMetrics.get(suggestion2) ? 1 : -1; 60 } 61 }; 62 SuggestionRanker(Context context, SuggestionFeaturizer suggestionFeaturizer)63 public SuggestionRanker(Context context, SuggestionFeaturizer suggestionFeaturizer) { 64 mSuggestionFeaturizer = suggestionFeaturizer; 65 mRelevanceMetrics = new HashMap<>(); 66 mMaxSuggestionsDisplayCount = FeatureFactory.get(context).experimentFeatureProvider() 67 .getMaxSuggestionDisplayCount(context); 68 } 69 70 /** 71 * Filter out suggestions that are not relevant at the moment, and rank the rest. 72 * 73 * @return a list of suggestion ranked by relevance. 74 */ rankRelevantSuggestions(List<Suggestion> suggestions)75 public List<Suggestion> rankRelevantSuggestions(List<Suggestion> suggestions) { 76 mRelevanceMetrics.clear(); 77 Map<String, Map<String, Double>> features = mSuggestionFeaturizer.featurize(suggestions); 78 for (Suggestion suggestion : suggestions) { 79 mRelevanceMetrics.put(suggestion, getRelevanceMetric(features.get(suggestion.getId()))); 80 } 81 final List<Suggestion> rankedSuggestions = new ArrayList<>(); 82 rankedSuggestions.addAll(suggestions); 83 Collections.sort(rankedSuggestions, suggestionComparator); 84 85 if (rankedSuggestions.size() < mMaxSuggestionsDisplayCount) { 86 return rankedSuggestions; 87 } else { 88 final List<Suggestion> relevantSuggestions = new ArrayList<>(); 89 for (int i = 0; i < mMaxSuggestionsDisplayCount; i++) { 90 relevantSuggestions.add(rankedSuggestions.get(i)); 91 } 92 return relevantSuggestions; 93 } 94 } 95 96 @VisibleForTesting getRelevanceMetric(Map<String, Double> features)97 double getRelevanceMetric(Map<String, Double> features) { 98 double sum = 0; 99 if (features == null) { 100 return sum; 101 } 102 for (String feature : WEIGHTS.keySet()) { 103 sum += WEIGHTS.get(feature) * features.get(feature); 104 } 105 return sum; 106 } 107 }