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 
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Map;
26 
27 /**
28  * Creates a set of interaction features (i.e., metrics) to represent each setting suggestion. These
29  * features currently include normalized time from previous events (shown, dismissed and clicked)
30  * for any particular suggestion and also counts of these events. These features are used as signals
31  * to find the best ranking for suggestion items.
32  * <p/>
33  * Copied from packages/apps/Settings/src/.../dashboard/suggestions/SuggestionFeaturizer
34  */
35 public class SuggestionFeaturizer {
36     // Key of the features used for ranking.
37     public static final String FEATURE_IS_SHOWN = "is_shown";
38     public static final String FEATURE_IS_DISMISSED = "is_dismissed";
39     public static final String FEATURE_IS_CLICKED = "is_clicked";
40     public static final String FEATURE_TIME_FROM_LAST_SHOWN = "time_from_last_shown";
41     public static final String FEATURE_TIME_FROM_LAST_DISMISSED = "time_from_last_dismissed";
42     public static final String FEATURE_TIME_FROM_LAST_CLICKED = "time_from_last_clicked";
43     public static final String FEATURE_SHOWN_COUNT = "shown_count";
44     public static final String FEATURE_DISMISSED_COUNT = "dismissed_count";
45     public static final String FEATURE_CLICKED_COUNT = "clicked_count";
46 
47     // The following numbers are estimated from histograms.
48     public static final double TIME_NORMALIZATION_FACTOR = 2e10;
49     public static final double COUNT_NORMALIZATION_FACTOR = 500;
50 
51     private final SuggestionEventStore mEventStore;
52 
SuggestionFeaturizer(Context context)53     public SuggestionFeaturizer(Context context) {
54         mEventStore = SuggestionEventStore.get(context);
55     }
56 
57     /**
58      * Extracts the features for each package name.
59      *
60      * @return A Map containing the features, keyed by the package names. Each map value contains
61      * another map with key-value pairs of the features.
62      */
featurize(List<Suggestion> suggestions)63     public Map<String, Map<String, Double>> featurize(List<Suggestion> suggestions) {
64         Map<String, Map<String, Double>> features = new HashMap<>();
65         Long curTimeMs = System.currentTimeMillis();
66         final List<String> suggestionIds = new ArrayList<>(suggestions.size());
67         for (Suggestion suggestion : suggestions) {
68             suggestionIds.add(suggestion.getId());
69         }
70         for (String id : suggestionIds) {
71             Map<String, Double> featureMap = new HashMap<>();
72             features.put(id, featureMap);
73             Long lastShownTime = mEventStore.readMetric(id,
74                     SuggestionEventStore.EVENT_SHOWN,
75                     SuggestionEventStore.METRIC_LAST_EVENT_TIME);
76             Long lastDismissedTime = mEventStore.readMetric(id,
77                     SuggestionEventStore.EVENT_DISMISSED,
78                     SuggestionEventStore.METRIC_LAST_EVENT_TIME);
79             Long lastClickedTime = mEventStore.readMetric(id,
80                     SuggestionEventStore.EVENT_CLICKED,
81                     SuggestionEventStore.METRIC_LAST_EVENT_TIME);
82             featureMap.put(FEATURE_IS_SHOWN, booleanToDouble(lastShownTime > 0));
83             featureMap.put(FEATURE_IS_DISMISSED, booleanToDouble(lastDismissedTime > 0));
84             featureMap.put(FEATURE_IS_CLICKED, booleanToDouble(lastClickedTime > 0));
85             featureMap.put(FEATURE_TIME_FROM_LAST_SHOWN,
86                     normalizedTimeDiff(curTimeMs, lastShownTime));
87             featureMap.put(FEATURE_TIME_FROM_LAST_DISMISSED,
88                     normalizedTimeDiff(curTimeMs, lastDismissedTime));
89             featureMap.put(FEATURE_TIME_FROM_LAST_CLICKED,
90                     normalizedTimeDiff(curTimeMs, lastClickedTime));
91             featureMap.put(FEATURE_SHOWN_COUNT, normalizedCount(mEventStore.readMetric(id,
92                     SuggestionEventStore.EVENT_SHOWN, SuggestionEventStore.METRIC_COUNT)));
93             featureMap.put(FEATURE_DISMISSED_COUNT, normalizedCount(mEventStore.readMetric(id,
94                     SuggestionEventStore.EVENT_DISMISSED, SuggestionEventStore.METRIC_COUNT)));
95             featureMap.put(FEATURE_CLICKED_COUNT, normalizedCount(mEventStore.readMetric(id,
96                     SuggestionEventStore.EVENT_CLICKED, SuggestionEventStore.METRIC_COUNT)));
97         }
98         return features;
99     }
100 
booleanToDouble(boolean bool)101     private static double booleanToDouble(boolean bool) {
102         return bool ? 1 : 0;
103     }
104 
normalizedTimeDiff(long curTimeMs, long preTimeMs)105     private static double normalizedTimeDiff(long curTimeMs, long preTimeMs) {
106         return Math.min(1, (curTimeMs - preTimeMs) / TIME_NORMALIZATION_FACTOR);
107     }
108 
normalizedCount(long count)109     private static double normalizedCount(long count) {
110         return Math.min(1, count / COUNT_NORMALIZATION_FACTOR);
111     }
112 }