1 /*
2  * Copyright (C) 2021 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 #include "host/libs/confui/host_renderer.h"
18 
19 #include <drm/drm_fourcc.h>
20 
21 #include "host/libs/config/cuttlefish_config.h"
22 
23 namespace cuttlefish {
24 namespace confui {
alfaCombineChannel(std::uint32_t shift,double alfa,teeui::Color a,teeui::Color b)25 static teeui::Color alfaCombineChannel(std::uint32_t shift, double alfa,
26                                        teeui::Color a, teeui::Color b) {
27   a >>= shift;
28   a &= 0xff;
29   b >>= shift;
30   b &= 0xff;
31   double acc = alfa * a + (1 - alfa) * b;
32   if (acc <= 0) {
33     return 0;
34   }
35   std::uint32_t result = acc;
36   if (result > 255) {
37     return 255 << shift;
38   }
39   return result << shift;
40 }
41 
42 /**
43  * create a raw frame for confirmation UI dialog
44  *
45  * Many rendering code borrowed from the following source
46  *  https://android.googlesource.com/trusty/app/confirmationui/+/0429cc7/src
47  */
48 class ConfUiRendererImpl {
49   friend class ConfUiRenderer;
50 
51  public:
52   using LabelConfMsg = teeui::LabelBody;
53 
54  private:
55   static Result<std::unique_ptr<ConfUiRendererImpl>> GenerateRenderer(
56       const std::uint32_t display, const std::string& confirmation_msg,
57       const std::string& locale, const bool inverted, const bool magnified);
58 
59   /**
60    * this does not repaint from the scratch all the time
61    *
62    * It does repaint its frame buffer only when w/h of
63    * current display has changed
64    */
65   std::unique_ptr<TeeUiFrameWrapper>& RenderRawFrame();
66 
IsFrameReady() const67   bool IsFrameReady() const { return raw_frame_ && !raw_frame_->IsEmpty(); }
68 
IsInConfirm(const std::uint32_t x,const std::uint32_t y)69   bool IsInConfirm(const std::uint32_t x, const std::uint32_t y) {
70     return IsInside<teeui::LabelOK>(x, y);
71   }
IsInCancel(const std::uint32_t x,const std::uint32_t y)72   bool IsInCancel(const std::uint32_t x, const std::uint32_t y) {
73     return IsInside<teeui::LabelCancel>(x, y);
74   }
75 
IsSetUpSuccessful() const76   bool IsSetUpSuccessful() const { return is_setup_well_; }
77   ConfUiRendererImpl(const std::uint32_t display,
78                      const std::string& confirmation_msg,
79                      const std::string& locale, const bool inverted,
80                      const bool magnified);
81 
82   struct Boundary {            // inclusive but.. LayoutElement's size is float
83     std::uint32_t x, y, w, h;  // (x, y) is the top left
84   };
85 
86   template <typename LayoutElement>
GetBoundary(LayoutElement && e) const87   Boundary GetBoundary(LayoutElement&& e) const {
88     auto box = e.bounds_;
89     Boundary b;
90     // (x,y) is left top. so floor() makes sense
91     // w, h are width and height in float. perhaps ceiling makes more
92     // sense
93     b.x = static_cast<std::uint32_t>(box.x().floor().count());
94     b.y = static_cast<std::uint32_t>(box.y().floor().count());
95     b.w = static_cast<std::uint32_t>(box.w().ceil().count());
96     b.h = static_cast<std::uint32_t>(box.h().ceil().count());
97     return b;
98   }
99 
100   template <typename Element>
IsInside(const std::uint32_t x,const std::uint32_t y) const101   bool IsInside(const std::uint32_t x, const std::uint32_t y) const {
102     auto box = GetBoundary(std::get<Element>(layout_));
103     if (x >= box.x && x <= box.x + box.w && y >= box.y && y <= box.y + box.h) {
104       return true;
105     }
106     return false;
107   }
108   // essentially, to repaint from the scratch, so returns new frame
109   // when successful. Or, nullopt
110   std::unique_ptr<TeeUiFrameWrapper> RepaintRawFrame(const int w, const int h);
111 
112   bool InitLayout(const std::string& lang_id);
113   teeui::Error UpdateTranslations();
114   teeui::Error UpdateLocale();
115   void SetDeviceContext(const unsigned long long w, const unsigned long long h,
116                         bool is_inverted, bool is_magnified);
117 
118   // a callback function to be effectively sent to TeeUI library
119   teeui::Error UpdatePixels(TeeUiFrameWrapper& buffer, std::uint32_t x,
120                             std::uint32_t y, teeui::Color color);
121 
122   // second param is for type deduction
123   template <typename... Elements>
drawElements(std::tuple<Elements...> & layout,const teeui::PixelDrawer & drawPixel)124   static teeui::Error drawElements(std::tuple<Elements...>& layout,
125                                    const teeui::PixelDrawer& drawPixel) {
126     // Error::operator|| is overloaded, so we don't get short circuit
127     // evaluation. But we get the first error that occurs. We will still try and
128     // draw the remaining elements in the order they appear in the layout tuple.
129     return (std::get<Elements>(layout).draw(drawPixel) || ...);
130   }
131   void UpdateColorScheme(const bool is_inverted);
132   template <typename Label>
SetText(const std::string & text)133   auto SetText(const std::string& text) {
134     return std::get<Label>(layout_).setText(
135         {text.c_str(), text.c_str() + text.size()});
136   }
137 
138   template <typename Label>
139   teeui::Error UpdateString();
140 
141   std::uint32_t display_num_;
142   teeui::layout_t<teeui::ConfUILayout> layout_;
143   std::string lang_id_;
144   std::string prompt_text_;  // confirmation ui message
145 
146   /**
147    * Potentially, the same frame could be requested multiple times.
148    *
149    * While another thread/caller is using this frame, the frame should
150    * be kept here, too, to be returned upon future requests.
151    *
152    */
153   std::unique_ptr<TeeUiFrameWrapper> raw_frame_;
154   std::uint32_t current_height_;
155   std::uint32_t current_width_;
156   teeui::Color color_bg_;
157   teeui::Color color_text_;
158   teeui::Color shield_color_;
159   bool is_inverted_;
160   bool is_magnified_;
161   teeui::context<teeui::ConfUIParameters> ctx_;
162   bool is_setup_well_;
163 
164   static constexpr const teeui::Color kColorBackground = 0xffffffff;
165   static constexpr const teeui::Color kColorBackgroundInv = 0xff212121;
166   static constexpr const teeui::Color kColorDisabled = 0xffbdbdbd;
167   static constexpr const teeui::Color kColorDisabledInv = 0xff424242;
168   static constexpr const teeui::Color kColorEnabled = 0xff212121;
169   static constexpr const teeui::Color kColorEnabledInv = 0xffdedede;
170   static constexpr const teeui::Color kColorShield = 0xff778500;
171   static constexpr const teeui::Color kColorShieldInv = 0xffc4cb80;
172   static constexpr const teeui::Color kColorText = 0xff212121;
173   static constexpr const teeui::Color kColorTextInv = 0xffdedede;
174 };
175 
176 Result<std::unique_ptr<ConfUiRendererImpl>>
GenerateRenderer(const std::uint32_t display,const std::string & confirmation_msg,const std::string & locale,const bool inverted,const bool magnified)177 ConfUiRendererImpl::GenerateRenderer(const std::uint32_t display,
178                                      const std::string& confirmation_msg,
179                                      const std::string& locale,
180                                      const bool inverted,
181                                      const bool magnified) {
182   ConfUiRendererImpl* raw_ptr = new ConfUiRendererImpl(
183       display, confirmation_msg, locale, inverted, magnified);
184   CF_EXPECT(raw_ptr && raw_ptr->IsSetUpSuccessful(),
185             "Failed to create ConfUiRendererImpl");
186   return std::unique_ptr<ConfUiRendererImpl>(raw_ptr);
187 }
188 
GetDpi(const int display_num=0)189 static int GetDpi(const int display_num = 0) {
190   auto config = CuttlefishConfig::Get();
191   CHECK(config) << "Config is Missing";
192   auto instance = config->ForDefaultInstance();
193   auto display_configs = instance.display_configs();
194   CHECK_GT(display_configs.size(), display_num)
195       << "Invalid display number " << display_num;
196   return display_configs[display_num].dpi;
197 }
198 
199 /**
200  * device configuration
201  *
202  * ctx_{# of pixels in 1 mm, # of pixels per 1 density independent pixels}
203  *
204  * The numbers are, however, to fit for the host webRTC local/remote clients
205  * in general, not necessarily the supposedly guest device (e.g. Auto, phone,
206  * etc)
207  *
208  * In general, for a normal PC, roughly ctx_(6.45211, 400.0/412.0) is a good
209  * combination for the default DPI, 320. If we want to see the impact
210  * of the change in the guest DPI, we could adjust the combination above
211  * proportionally
212  *
213  */
ConfUiRendererImpl(const std::uint32_t display,const std::string & confirmation_msg,const std::string & locale,const bool inverted,const bool magnified)214 ConfUiRendererImpl::ConfUiRendererImpl(const std::uint32_t display,
215                                        const std::string& confirmation_msg,
216                                        const std::string& locale,
217                                        const bool inverted,
218                                        const bool magnified)
219     : display_num_{display},
220       lang_id_{locale},
221       prompt_text_{confirmation_msg},
222       current_height_{ScreenConnectorInfo::ScreenHeight(display_num_)},
223       current_width_{ScreenConnectorInfo::ScreenWidth(display_num_)},
224       is_inverted_(inverted),
225       is_magnified_(magnified),
226       ctx_(6.45211 * GetDpi() / 320.0, 400.0 / 412.0 * GetDpi() / 320.0),
227       is_setup_well_(false) {
228   SetDeviceContext(current_width_, current_height_, is_inverted_,
229                    is_magnified_);
230   layout_ = teeui::instantiateLayout(teeui::ConfUILayout(), ctx_);
231 
232   if (auto error = UpdateLocale()) {
233     ConfUiLog(ERROR) << "Update Translation Error: " << Enum2Base(error.code());
234     // is_setup_well_ = false;
235     return;
236   }
237   UpdateColorScheme(is_inverted_);
238   SetText<LabelConfMsg>(prompt_text_);
239   is_setup_well_ = true;
240 }
241 
UpdateLocale()242 teeui::Error ConfUiRendererImpl::UpdateLocale() {
243   using teeui::Error;
244   teeui::localization::selectLangId(lang_id_.c_str());
245   if (auto error = UpdateTranslations()) {
246     return error;
247   }
248   return Error::OK;
249 }
250 
251 template <typename Label>
UpdateString()252 teeui::Error ConfUiRendererImpl::UpdateString() {
253   using namespace teeui;
254   const char* str;
255   auto& label = std::get<Label>(layout_);
256   str = localization::lookup(TranslationId(label.textId()));
257   if (str == nullptr) {
258     ConfUiLog(ERROR) << "Given translation_id" << label.textId() << "not found";
259     return Error::Localization;
260   }
261   label.setText({str, str + strlen(str)});
262   return Error::OK;
263 }
264 
UpdateTranslations()265 teeui::Error ConfUiRendererImpl::UpdateTranslations() {
266   using namespace teeui;
267   if (auto error = UpdateString<LabelOK>()) {
268     return error;
269   }
270   if (auto error = UpdateString<LabelCancel>()) {
271     return error;
272   }
273   if (auto error = UpdateString<LabelTitle>()) {
274     return error;
275   }
276   if (auto error = UpdateString<LabelHint>()) {
277     return error;
278   }
279   return Error::OK;
280 }
281 
SetDeviceContext(const unsigned long long w,const unsigned long long h,const bool is_inverted,const bool is_magnified)282 void ConfUiRendererImpl::SetDeviceContext(const unsigned long long w,
283                                           const unsigned long long h,
284                                           const bool is_inverted,
285                                           const bool is_magnified) {
286   using namespace teeui;
287   const auto screen_width = operator""_px(w);
288   const auto screen_height = operator""_px(h);
289   ctx_.setParam<RightEdgeOfScreen>(pxs(screen_width));
290   ctx_.setParam<BottomOfScreen>(pxs(screen_height));
291   if (is_magnified) {
292     ctx_.setParam<DefaultFontSize>(18_dp);
293     ctx_.setParam<BodyFontSize>(20_dp);
294   } else {
295     ctx_.setParam<DefaultFontSize>(14_dp);
296     ctx_.setParam<BodyFontSize>(16_dp);
297   }
298   if (is_inverted) {
299     ctx_.setParam<ShieldColor>(kColorShieldInv);
300     ctx_.setParam<ColorText>(kColorTextInv);
301     ctx_.setParam<ColorBG>(kColorBackgroundInv);
302     ctx_.setParam<ColorButton>(kColorShieldInv);
303   } else {
304     ctx_.setParam<ShieldColor>(kColorShield);
305     ctx_.setParam<ColorText>(kColorText);
306     ctx_.setParam<ColorBG>(kColorBackground);
307     ctx_.setParam<ColorButton>(kColorShield);
308   }
309 }
310 
UpdatePixels(TeeUiFrameWrapper & raw_frame,std::uint32_t x,std::uint32_t y,teeui::Color color)311 teeui::Error ConfUiRendererImpl::UpdatePixels(TeeUiFrameWrapper& raw_frame,
312                                               std::uint32_t x, std::uint32_t y,
313                                               teeui::Color color) {
314   auto buffer = raw_frame.data();
315   const auto height = raw_frame.Height();
316   const auto width = raw_frame.Width();
317   auto pos = width * y + x;
318   if (pos >= (height * width)) {
319     ConfUiLog(ERROR) << "Rendering Out of Bound";
320     return teeui::Error::OutOfBoundsDrawing;
321   }
322   const double alfa = ((color & 0xff000000) >> 24) / 255.0;
323   auto& pixel = *reinterpret_cast<teeui::Color*>(buffer + pos);
324   pixel = alfaCombineChannel(0, alfa, color, pixel) |
325           alfaCombineChannel(8, alfa, color, pixel) |
326           alfaCombineChannel(16, alfa, color, pixel);
327   return teeui::Error::OK;
328 }
329 
UpdateColorScheme(const bool is_inverted)330 void ConfUiRendererImpl::UpdateColorScheme(const bool is_inverted) {
331   using namespace teeui;
332   color_text_ = is_inverted ? kColorDisabledInv : kColorDisabled;
333   shield_color_ = is_inverted ? kColorShieldInv : kColorShield;
334   color_bg_ = is_inverted ? kColorBackgroundInv : kColorBackground;
335 
336   ctx_.setParam<ShieldColor>(shield_color_);
337   ctx_.setParam<ColorText>(color_text_);
338   ctx_.setParam<ColorBG>(color_bg_);
339   return;
340 }
341 
RenderRawFrame()342 std::unique_ptr<TeeUiFrameWrapper>& ConfUiRendererImpl::RenderRawFrame() {
343   /* we repaint only if one or more of the following meet:
344    *
345    *  1. raw_frame_ is empty
346    *  2. the current_width_ and current_height_ is out of date
347    *
348    */
349   const int w = ScreenConnectorInfo::ScreenWidth(display_num_);
350   const int h = ScreenConnectorInfo::ScreenHeight(display_num_);
351   if (!IsFrameReady() || current_height_ != h || current_width_ != w) {
352     auto new_frame = RepaintRawFrame(w, h);
353     if (!new_frame) {
354       // must repaint but failed
355       raw_frame_ = nullptr;
356       return raw_frame_;
357     }
358     // repainting from the scratch successful in a new frame
359     raw_frame_ = std::move(new_frame);
360     current_width_ = w;
361     current_height_ = h;
362   }
363   return raw_frame_;
364 }
365 
RepaintRawFrame(const int w,const int h)366 std::unique_ptr<TeeUiFrameWrapper> ConfUiRendererImpl::RepaintRawFrame(
367     const int w, const int h) {
368   std::get<teeui::LabelOK>(layout_).setTextColor(kColorEnabled);
369   std::get<teeui::LabelCancel>(layout_).setTextColor(kColorEnabled);
370 
371   /**
372    * should be uint32_t for teeui APIs.
373    * It assumes that each raw frame buffer element is 4 bytes
374    */
375   const teeui::Color background_color =
376       is_inverted_ ? kColorBackgroundInv : kColorBackground;
377   auto new_raw_frame =
378       std::make_unique<TeeUiFrameWrapper>(w, h, background_color);
379   auto draw_pixel = teeui::makePixelDrawer(
380       [this, &new_raw_frame](std::uint32_t x, std::uint32_t y,
381                              teeui::Color color) -> teeui::Error {
382         return this->UpdatePixels(*new_raw_frame, x, y, color);
383       });
384 
385   // render all components
386   const auto error = drawElements(layout_, draw_pixel);
387   if (error) {
388     ConfUiLog(ERROR) << "Painting failed: " << error.code();
389     return nullptr;
390   }
391 
392   return new_raw_frame;
393 }
394 
ConfUiRenderer(ScreenConnectorFrameRenderer & screen_connector)395 ConfUiRenderer::ConfUiRenderer(ScreenConnectorFrameRenderer& screen_connector)
396     : screen_connector_{screen_connector} {}
397 
~ConfUiRenderer()398 ConfUiRenderer::~ConfUiRenderer() {}
399 
RenderDialog(const std::uint32_t display_num,const std::string & prompt_text,const std::string & locale,const std::vector<teeui::UIOption> & ui_options)400 Result<void> ConfUiRenderer::RenderDialog(
401     const std::uint32_t display_num, const std::string& prompt_text,
402     const std::string& locale, const std::vector<teeui::UIOption>& ui_options) {
403   renderer_impl_ = CF_EXPECT(ConfUiRendererImpl::GenerateRenderer(
404       display_num, prompt_text, locale, IsInverted(ui_options),
405       IsMagnified(ui_options)));
406   auto& teeui_frame = renderer_impl_->RenderRawFrame();
407   CF_EXPECT(teeui_frame != nullptr, "RenderRawFrame() failed.");
408   ConfUiLog(VERBOSE) << "actually trying to render the frame"
409                      << thread::GetName();
410   auto frame_width = teeui_frame->Width();
411   auto frame_height = teeui_frame->Height();
412   auto frame_stride_bytes = teeui_frame->ScreenStrideBytes();
413   auto frame_bytes = reinterpret_cast<std::uint8_t*>(teeui_frame->data());
414   CF_EXPECT(screen_connector_.RenderConfirmationUi(
415       display_num, frame_width, frame_height, DRM_FORMAT_ABGR8888,
416       frame_stride_bytes, frame_bytes));
417   return {};
418 }
419 
IsInverted(const std::vector<teeui::UIOption> & ui_options) const420 bool ConfUiRenderer::IsInverted(
421     const std::vector<teeui::UIOption>& ui_options) const {
422   return Contains(ui_options, teeui::UIOption::AccessibilityInverted);
423 }
424 
IsMagnified(const std::vector<teeui::UIOption> & ui_options) const425 bool ConfUiRenderer::IsMagnified(
426     const std::vector<teeui::UIOption>& ui_options) const {
427   return Contains(ui_options, teeui::UIOption::AccessibilityMagnified);
428 }
429 
IsInConfirm(const std::uint32_t x,const std::uint32_t y)430 bool ConfUiRenderer::IsInConfirm(const std::uint32_t x, const std::uint32_t y) {
431   if (!renderer_impl_) {
432     ConfUiLog(INFO) << "renderer_impl_ is nullptr";
433   }
434   return renderer_impl_ && renderer_impl_->IsInConfirm(x, y);
435 }
IsInCancel(const std::uint32_t x,const std::uint32_t y)436 bool ConfUiRenderer::IsInCancel(const std::uint32_t x, const std::uint32_t y) {
437   if (!renderer_impl_) {
438     ConfUiLog(INFO) << "renderer_impl_ is nullptr";
439   }
440   return renderer_impl_ && renderer_impl_->IsInCancel(x, y);
441 }
442 
443 }  // end of namespace confui
444 }  // end of namespace cuttlefish
445