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 "chromeos/services/assistant/assistant_device_settings_delegate.h"
6 
7 #include <algorithm>
8 #include <memory>
9 #include <utility>
10 
11 #include "ash/public/cpp/assistant/controller/assistant_notification_controller.h"
12 #include "base/logging.h"
13 #include "base/memory/weak_ptr.h"
14 #include "chromeos/assistant/internal/internal_util.h"
15 #include "chromeos/assistant/internal/proto/google3/assistant/api/client_op/device_args.pb.h"
16 #include "chromeos/services/assistant/cros_platform_api.h"
17 #include "chromeos/services/assistant/public/cpp/assistant_service.h"
18 #include "chromeos/services/assistant/public/cpp/device_actions.h"
19 #include "chromeos/services/assistant/service_context.h"
20 #include "libassistant/shared/public/platform_audio_output.h"
21 
22 namespace client_op = ::assistant::api::client_op;
23 
24 namespace chromeos {
25 namespace assistant {
26 
27 class Setting {
28  public:
29   Setting() = default;
30   Setting(Setting&) = delete;
31   Setting& operator=(Setting&) = delete;
32   virtual ~Setting() = default;
33 
34   virtual const char* setting_id() const = 0;
35   virtual void Modify(const client_op::ModifySettingArgs& request) = 0;
36 };
37 
38 namespace {
39 
40 constexpr char kWiFiDeviceSettingId[] = "WIFI";
41 constexpr char kBluetoothDeviceSettingId[] = "BLUETOOTH";
42 constexpr char kScreenBrightnessDeviceSettingId[] = "BRIGHTNESS_LEVEL";
43 constexpr char kDoNotDisturbDeviceSettingId[] = "DO_NOT_DISTURB";
44 constexpr char kNightLightDeviceSettingId[] = "NIGHT_LIGHT_SWITCH";
45 constexpr char kSwitchAccessDeviceSettingId[] = "SWITCH_ACCESS";
46 
47 constexpr float kDefaultSliderStep = 0.1f;
48 
LogUnsupportedChange(client_op::ModifySettingArgs args)49 void LogUnsupportedChange(client_op::ModifySettingArgs args) {
50   LOG(ERROR) << "Unsupported change operation: " << args.change()
51              << " for setting " << args.setting_id();
52 }
53 
HandleOnOffChange(client_op::ModifySettingArgs modify_setting_args,std::function<void (bool)> on_off_handler)54 void HandleOnOffChange(client_op::ModifySettingArgs modify_setting_args,
55                        std::function<void(bool)> on_off_handler) {
56   switch (modify_setting_args.change()) {
57     case client_op::ModifySettingArgs_Change_ON:
58       on_off_handler(true);
59       return;
60     case client_op::ModifySettingArgs_Change_OFF:
61       on_off_handler(false);
62       return;
63 
64     // Currently there are no use-cases for toggling.  This could change in the
65     // future.
66     case client_op::ModifySettingArgs_Change_TOGGLE:
67       break;
68 
69     case client_op::ModifySettingArgs_Change_SET:
70     case client_op::ModifySettingArgs_Change_INCREASE:
71     case client_op::ModifySettingArgs_Change_DECREASE:
72     case client_op::ModifySettingArgs_Change_UNSPECIFIED:
73       // This shouldn't happen.
74       break;
75   }
76   LogUnsupportedChange(modify_setting_args);
77 }
78 
79 // Helper function that converts a slider value sent from the server, either
80 // absolute or a delta, from a given unit (e.g., STEP), to a percentage.
ConvertSliderValueToLevel(double value,client_op::ModifySettingArgs_Unit unit,double default_value)81 double ConvertSliderValueToLevel(double value,
82                                  client_op::ModifySettingArgs_Unit unit,
83                                  double default_value) {
84   switch (unit) {
85     case client_op::ModifySettingArgs_Unit_RANGE:
86       // "set brightness to 20%".
87       return value;
88     case client_op::ModifySettingArgs_Unit_STEP:
89       // "set brightness to 20".  Treat the step as a percentage.
90       return value / 100.0f;
91 
92     // Currently, factor (e.g., 'double the brightness') and decibel units
93     // aren't handled by the backend.  This could change in the future.
94     case client_op::ModifySettingArgs_Unit_FACTOR:
95     case client_op::ModifySettingArgs_Unit_DECIBEL:
96       break;
97 
98     case client_op::ModifySettingArgs_Unit_NATIVE:
99     case client_op::ModifySettingArgs_Unit_UNKNOWN_UNIT:
100       // This shouldn't happen.
101       break;
102   }
103   LOG(ERROR) << "Unsupported slider unit: " << unit;
104   return default_value;
105 }
106 
HandleSliderChange(client_op::ModifySettingArgs request,std::function<void (double)> set_value_handler,std::function<double ()> get_value_handler)107 void HandleSliderChange(client_op::ModifySettingArgs request,
108                         std::function<void(double)> set_value_handler,
109                         std::function<double()> get_value_handler) {
110   switch (request.change()) {
111     case client_op::ModifySettingArgs_Change_SET: {
112       // For unsupported units, set the value to the current value, for
113       // visual feedback.
114       double new_value = ConvertSliderValueToLevel(
115           request.numeric_value(), request.unit(), get_value_handler());
116       set_value_handler(new_value);
117       return;
118     }
119 
120     case client_op::ModifySettingArgs_Change_INCREASE:
121     case client_op::ModifySettingArgs_Change_DECREASE: {
122       double current_value = get_value_handler();
123       double step = kDefaultSliderStep;
124       if (request.numeric_value() != 0.0f) {
125         // For unsupported units, use the default step percentage.
126         step = ConvertSliderValueToLevel(request.numeric_value(),
127                                          request.unit(), kDefaultSliderStep);
128       }
129       double new_value =
130           (request.change() == client_op::ModifySettingArgs_Change_INCREASE)
131               ? std::min(current_value + step, 1.0)
132               : std::max(current_value - step, 0.0);
133       set_value_handler(new_value);
134       return;
135     }
136 
137     case client_op::ModifySettingArgs_Change_ON:
138     case client_op::ModifySettingArgs_Change_OFF:
139     case client_op::ModifySettingArgs_Change_TOGGLE:
140     case client_op::ModifySettingArgs_Change_UNSPECIFIED:
141       // This shouldn't happen.
142       break;
143   }
144   LogUnsupportedChange(request);
145 }
146 
147 class SettingWithDeviceAction : public Setting {
148  public:
SettingWithDeviceAction(ServiceContext * context)149   explicit SettingWithDeviceAction(ServiceContext* context)
150       : context_(context) {}
151 
device_actions()152   DeviceActions* device_actions() { return context_->device_actions(); }
153 
154  private:
155   ServiceContext* context_;
156 };
157 
158 class WifiSetting : public SettingWithDeviceAction {
159  public:
160   using SettingWithDeviceAction::SettingWithDeviceAction;
161 
setting_id() const162   const char* setting_id() const override { return kWiFiDeviceSettingId; }
163 
Modify(const client_op::ModifySettingArgs & request)164   void Modify(const client_op::ModifySettingArgs& request) override {
165     HandleOnOffChange(request, [&](bool enabled) {
166       this->device_actions()->SetWifiEnabled(enabled);
167     });
168   }
169 };
170 
171 class BluetoothSetting : public SettingWithDeviceAction {
172  public:
173   using SettingWithDeviceAction::SettingWithDeviceAction;
174 
setting_id() const175   const char* setting_id() const override { return kBluetoothDeviceSettingId; }
176 
Modify(const client_op::ModifySettingArgs & request)177   void Modify(const client_op::ModifySettingArgs& request) override {
178     HandleOnOffChange(request, [&](bool enabled) {
179       this->device_actions()->SetBluetoothEnabled(enabled);
180     });
181   }
182 };
183 
184 class DoNotDisturbSetting : public Setting {
185  public:
DoNotDisturbSetting(ServiceContext * context)186   explicit DoNotDisturbSetting(ServiceContext* context) : context_(context) {}
187 
setting_id() const188   const char* setting_id() const override {
189     return kDoNotDisturbDeviceSettingId;
190   }
191 
Modify(const client_op::ModifySettingArgs & request)192   void Modify(const client_op::ModifySettingArgs& request) override {
193     HandleOnOffChange(request, [&](bool enabled) {
194       this->assistant_notification_controller()->SetQuietMode(enabled);
195     });
196   }
197 
198  private:
assistant_notification_controller()199   ash::AssistantNotificationController* assistant_notification_controller() {
200     return context_->assistant_notification_controller();
201   }
202 
203   ServiceContext* context_;
204 };
205 
206 class SwitchAccessSetting : public SettingWithDeviceAction {
207  public:
208   using SettingWithDeviceAction::SettingWithDeviceAction;
209 
setting_id() const210   const char* setting_id() const override {
211     return kSwitchAccessDeviceSettingId;
212   }
213 
Modify(const client_op::ModifySettingArgs & request)214   void Modify(const client_op::ModifySettingArgs& request) override {
215     HandleOnOffChange(request, [&](bool enabled) {
216       this->device_actions()->SetSwitchAccessEnabled(enabled);
217     });
218   }
219 };
220 
221 class NightLightSetting : public SettingWithDeviceAction {
222  public:
223   using SettingWithDeviceAction::SettingWithDeviceAction;
224 
setting_id() const225   const char* setting_id() const override { return kNightLightDeviceSettingId; }
226 
Modify(const client_op::ModifySettingArgs & request)227   void Modify(const client_op::ModifySettingArgs& request) override {
228     HandleOnOffChange(request, [&](bool enabled) {
229       this->device_actions()->SetNightLightEnabled(enabled);
230     });
231   }
232 };
233 
234 class BrightnessSetting : public SettingWithDeviceAction {
235  public:
BrightnessSetting(ServiceContext * context)236   explicit BrightnessSetting(ServiceContext* context)
237       : SettingWithDeviceAction(context), weak_factory_(this) {}
238 
setting_id() const239   const char* setting_id() const override {
240     return kScreenBrightnessDeviceSettingId;
241   }
242 
Modify(const client_op::ModifySettingArgs & request)243   void Modify(const client_op::ModifySettingArgs& request) override {
244     device_actions()->GetScreenBrightnessLevel(base::BindOnce(
245         [](base::WeakPtr<BrightnessSetting> this_,
246            client_op::ModifySettingArgs request, bool success,
247            double current_value) {
248           if (!success || !this_) {
249             LOG(WARNING) << "Failed to get brightness level";
250             return;
251           }
252           HandleSliderChange(
253               request,
254               [&this_](double new_value) {
255                 this_->device_actions()->SetScreenBrightnessLevel(new_value,
256                                                                   true);
257               },
258               [current_value]() { return current_value; });
259         },
260         weak_factory_.GetWeakPtr(), request));
261   }
262 
263  private:
264   base::WeakPtrFactory<BrightnessSetting> weak_factory_;
265 };
266 
267 }  // namespace
268 
AssistantDeviceSettingsDelegate(ServiceContext * context)269 AssistantDeviceSettingsDelegate::AssistantDeviceSettingsDelegate(
270     ServiceContext* context) {
271   AddSetting(std::make_unique<WifiSetting>(context));
272   AddSetting(std::make_unique<BluetoothSetting>(context));
273   AddSetting(std::make_unique<NightLightSetting>(context));
274   AddSetting(std::make_unique<DoNotDisturbSetting>(context));
275   AddSetting(std::make_unique<BrightnessSetting>(context));
276   AddSetting(std::make_unique<SwitchAccessSetting>(context));
277 }
278 
279 AssistantDeviceSettingsDelegate::~AssistantDeviceSettingsDelegate() = default;
280 
IsSettingSupported(const std::string & setting_id) const281 bool AssistantDeviceSettingsDelegate::IsSettingSupported(
282     const std::string& setting_id) const {
283   return std::any_of(settings_.begin(), settings_.end(),
284                      [&setting_id](const auto& setting) {
285                        return setting->setting_id() == setting_id;
286                      });
287 }
288 
HandleModifyDeviceSetting(const client_op::ModifySettingArgs & modify_setting_args)289 void AssistantDeviceSettingsDelegate::HandleModifyDeviceSetting(
290     const client_op::ModifySettingArgs& modify_setting_args) {
291   VLOG(1) << "Assistant: Modifying Device Setting '"
292           << modify_setting_args.setting_id() << "'";
293   DCHECK(IsSettingSupported(modify_setting_args.setting_id()));
294 
295   for (const auto& setting : settings_) {
296     if (setting->setting_id() == modify_setting_args.setting_id()) {
297       setting->Modify(modify_setting_args);
298       return;
299     }
300   }
301 
302   NOTREACHED();
303 }
304 
GetDeviceSettings(const::assistant::api::client_op::GetDeviceSettingsArgs & args) const305 std::vector<DeviceSetting> AssistantDeviceSettingsDelegate::GetDeviceSettings(
306     const ::assistant::api::client_op::GetDeviceSettingsArgs& args) const {
307   std::vector<DeviceSetting> result;
308   for (const std::string& setting_id : args.setting_ids())
309     result.emplace_back(setting_id, IsSettingSupported(setting_id));
310   return result;
311 }
312 
AddSetting(std::unique_ptr<Setting> setting)313 void AssistantDeviceSettingsDelegate::AddSetting(
314     std::unique_ptr<Setting> setting) {
315   settings_.push_back(std::move(setting));
316 }
317 
318 }  // namespace assistant
319 }  // namespace chromeos
320