1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/permissions/prediction_based_permission_ui_selector.h"
6 
7 #include "base/command_line.h"
8 #include "base/feature_list.h"
9 #include "base/time/default_clock.h"
10 #include "base/util/values/values_util.h"
11 #include "chrome/browser/permissions/prediction_service_factory.h"
12 #include "chrome/browser/permissions/prediction_service_request.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "chrome/common/chrome_features.h"
15 #include "chrome/common/chrome_switches.h"
16 #include "chrome/common/pref_names.h"
17 #include "components/permissions/permission_util.h"
18 #include "components/permissions/prediction_service/prediction_service.h"
19 #include "components/permissions/prediction_service/prediction_service_messages.pb.h"
20 #include "components/prefs/pref_service.h"
21 #include "components/safe_browsing/core/common/safe_browsing_prefs.h"
22 
23 namespace {
24 
25 using QuietUiReason = PredictionBasedPermissionUiSelector::QuietUiReason;
26 using Decision = PredictionBasedPermissionUiSelector::Decision;
27 
28 constexpr auto VeryUnlikely = permissions::
29     PermissionSuggestion_Likelihood_DiscretizedLikelihood_VERY_UNLIKELY;
30 
31 // The data we consider can only be at most 28 days old to match the data that
32 // the ML model is built on.
33 constexpr base::TimeDelta kPermissionActionCutoffAge =
34     base::TimeDelta::FromDays(28);
35 
36 constexpr char kPermissionActionEntryActionKey[] = "action";
37 constexpr char kPermissionActionEntryTimestampKey[] = "time";
38 
39 base::Optional<
40     permissions::PermissionSuggestion_Likelihood_DiscretizedLikelihood>
ParsePredictionServiceMockLikelihood(const std::string & value)41 ParsePredictionServiceMockLikelihood(const std::string& value) {
42   if (value == "very-unlikely") {
43     return permissions::
44         PermissionSuggestion_Likelihood_DiscretizedLikelihood_VERY_UNLIKELY;
45   } else if (value == "unlikely") {
46     return permissions::
47         PermissionSuggestion_Likelihood_DiscretizedLikelihood_UNLIKELY;
48   } else if (value == "neutral") {
49     return permissions::
50         PermissionSuggestion_Likelihood_DiscretizedLikelihood_NEUTRAL;
51   } else if (value == "likely") {
52     return permissions::
53         PermissionSuggestion_Likelihood_DiscretizedLikelihood_LIKELY;
54   } else if (value == "very-likely") {
55     return permissions::
56         PermissionSuggestion_Likelihood_DiscretizedLikelihood_VERY_LIKELY;
57   }
58 
59   return base::nullopt;
60 }
61 
ShouldPredictionTriggerQuietUi(permissions::PermissionUmaUtil::PredictionGrantLikelihood likelihood)62 bool ShouldPredictionTriggerQuietUi(
63     permissions::PermissionUmaUtil::PredictionGrantLikelihood likelihood) {
64   return likelihood == VeryUnlikely;
65 }
66 
67 }  // namespace
68 
PredictionBasedPermissionUiSelector(Profile * profile)69 PredictionBasedPermissionUiSelector::PredictionBasedPermissionUiSelector(
70     Profile* profile)
71     : profile_(profile) {
72   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
73           switches::kPredictionServiceMockLikelihood)) {
74     auto mock_likelihood = ParsePredictionServiceMockLikelihood(
75         base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
76             switches::kPredictionServiceMockLikelihood));
77     if (mock_likelihood.has_value())
78       set_likelihood_override(mock_likelihood.value());
79   }
80 }
81 
82 PredictionBasedPermissionUiSelector::~PredictionBasedPermissionUiSelector() =
83     default;
84 
SelectUiToUse(permissions::PermissionRequest * request,DecisionMadeCallback callback)85 void PredictionBasedPermissionUiSelector::SelectUiToUse(
86     permissions::PermissionRequest* request,
87     DecisionMadeCallback callback) {
88   if (!IsAllowedToUseAssistedPrompts()) {
89     std::move(callback).Run(Decision::UseNormalUiAndShowNoWarning());
90     return;
91   }
92 
93   if (likelihood_override_for_testing_.has_value()) {
94     if (ShouldPredictionTriggerQuietUi(
95             likelihood_override_for_testing_.value())) {
96       std::move(callback).Run(
97           Decision(QuietUiReason::kPredictedVeryUnlikelyGrant,
98                    Decision::ShowNoWarning()));
99     } else {
100       std::move(callback).Run(Decision::UseNormalUiAndShowNoWarning());
101     }
102     return;
103   }
104 
105   last_request_grant_likelihood_ = base::nullopt;
106 
107   DCHECK(!request_);
108   permissions::PredictionService* service =
109       PredictionServiceFactory::GetForProfile(profile_);
110   callback_ = std::move(callback);
111   request_ = std::make_unique<PredictionServiceRequest>(
112       service, BuildPredictionRequestFeatures(request),
113       base::BindOnce(
114           &PredictionBasedPermissionUiSelector::LookupReponseReceived,
115           base::Unretained(this)));
116 }
117 
Cancel()118 void PredictionBasedPermissionUiSelector::Cancel() {
119   request_.reset();
120   callback_.Reset();
121 }
122 
123 base::Optional<permissions::PermissionUmaUtil::PredictionGrantLikelihood>
PredictedGrantLikelihoodForUKM()124 PredictionBasedPermissionUiSelector::PredictedGrantLikelihoodForUKM() {
125   return last_request_grant_likelihood_;
126 }
127 
128 permissions::PredictionRequestFeatures
BuildPredictionRequestFeatures(permissions::PermissionRequest * request)129 PredictionBasedPermissionUiSelector::BuildPredictionRequestFeatures(
130     permissions::PermissionRequest* request) {
131   permissions::PredictionRequestFeatures features;
132   features.gesture = request->GetGestureType();
133   features.type = request->GetPermissionRequestType();
134   auto* permission_actions =
135       profile_->GetPrefs()->GetList(prefs::kNotificationPermissionActions);
136 
137   base::Time cutoff = base::Time::Now() - kPermissionActionCutoffAge;
138   for (const auto& action : *permission_actions) {
139     const base::Optional<base::Time> timestamp =
140         util::ValueToTime(action.FindKey(kPermissionActionEntryTimestampKey));
141 
142     if (!timestamp || *timestamp < cutoff)
143       continue;
144 
145     const base::Optional<int> past_action_as_int =
146         action.FindIntKey(kPermissionActionEntryActionKey);
147     DCHECK(past_action_as_int);
148 
149     const permissions::PermissionAction past_action =
150         static_cast<permissions::PermissionAction>(*past_action_as_int);
151 
152     // TODO(andypaicu): implement recording all prompts outcomes regardless of
153     // type. We currently only count notification prompts.
154     switch (past_action) {
155       case permissions::PermissionAction::DENIED:
156         features.requested_permission_counts.denies++;
157         features.all_permission_counts.denies++;
158         break;
159       case permissions::PermissionAction::GRANTED:
160         features.requested_permission_counts.grants++;
161         features.all_permission_counts.grants++;
162         break;
163       case permissions::PermissionAction::DISMISSED:
164         features.requested_permission_counts.dismissals++;
165         features.all_permission_counts.dismissals++;
166         break;
167       case permissions::PermissionAction::IGNORED:
168         features.requested_permission_counts.ignores++;
169         features.all_permission_counts.ignores++;
170         break;
171       default:
172         // Anything else is ignored.
173         break;
174     }
175   }
176 
177   return features;
178 }
179 
LookupReponseReceived(bool lookup_succesful,bool response_from_cache,std::unique_ptr<permissions::GetSuggestionsResponse> response)180 void PredictionBasedPermissionUiSelector::LookupReponseReceived(
181     bool lookup_succesful,
182     bool response_from_cache,
183     std::unique_ptr<permissions::GetSuggestionsResponse> response) {
184   request_.reset();
185   if (!lookup_succesful || !response || response->suggestion_size() == 0) {
186     std::move(callback_).Run(Decision::UseNormalUiAndShowNoWarning());
187     return;
188   }
189 
190   last_request_grant_likelihood_ =
191       response->suggestion(0).grant_likelihood().discretized_likelihood();
192 
193   if (ShouldPredictionTriggerQuietUi(last_request_grant_likelihood_.value())) {
194     std::move(callback_).Run(Decision(
195         QuietUiReason::kPredictedVeryUnlikelyGrant, Decision::ShowNoWarning()));
196     return;
197   }
198 
199   std::move(callback_).Run(Decision::UseNormalUiAndShowNoWarning());
200 }
201 
IsAllowedToUseAssistedPrompts()202 bool PredictionBasedPermissionUiSelector::IsAllowedToUseAssistedPrompts() {
203   // We need to also check `kQuietNotificationPrompts` here since there is no
204   // generic safeguard anywhere else in the stack.
205   return base::FeatureList::IsEnabled(features::kQuietNotificationPrompts) &&
206          base::FeatureList::IsEnabled(features::kPermissionPredictions) &&
207          safe_browsing::IsEnhancedProtectionEnabled(*(profile_->GetPrefs()));
208 }
209