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 = 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 
SuggestionRanker(Context context, SuggestionFeaturizer suggestionFeaturizer)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      */
rankRelevantSuggestions(List<Suggestion> suggestions)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
getRelevanceMetric(Map<String, Double> features)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 }