ambient_animation_view.cc 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. // Copyright 2022 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. #include "ash/ambient/ui/ambient_animation_view.h"
  5. #include <algorithm>
  6. #include <cstdlib>
  7. #include <utility>
  8. #include "ash/ambient/ambient_view_delegate_impl.h"
  9. #include "ash/ambient/metrics/ambient_multi_screen_metrics_recorder.h"
  10. #include "ash/ambient/model/ambient_animation_attribution_provider.h"
  11. #include "ash/ambient/model/ambient_backend_model.h"
  12. #include "ash/ambient/model/ambient_photo_config.h"
  13. #include "ash/ambient/resources/ambient_animation_resource_constants.h"
  14. #include "ash/ambient/resources/ambient_animation_static_resources.h"
  15. #include "ash/ambient/ui/ambient_animation_attribution_transformer.h"
  16. #include "ash/ambient/ui/ambient_animation_background_color.h"
  17. #include "ash/ambient/ui/ambient_animation_player.h"
  18. #include "ash/ambient/ui/ambient_animation_resizer.h"
  19. #include "ash/ambient/ui/ambient_animation_shield_controller.h"
  20. #include "ash/ambient/ui/ambient_view_ids.h"
  21. #include "ash/ambient/ui/glanceable_info_view.h"
  22. #include "ash/ambient/ui/media_string_view.h"
  23. #include "ash/ambient/util/ambient_util.h"
  24. #include "ash/public/cpp/ambient/ambient_metrics.h"
  25. #include "ash/public/cpp/metrics_util.h"
  26. #include "base/bind.h"
  27. #include "base/check.h"
  28. #include "base/location.h"
  29. #include "base/logging.h"
  30. #include "base/threading/sequenced_task_runner_handle.h"
  31. #include "base/time/time.h"
  32. #include "cc/metrics/frame_sequence_tracker.h"
  33. #include "cc/paint/skottie_color_map.h"
  34. #include "cc/paint/skottie_resource_metadata.h"
  35. #include "cc/paint/skottie_wrapper.h"
  36. #include "third_party/skia/include/core/SkColor.h"
  37. #include "ui/base/metadata/metadata_impl_macros.h"
  38. #include "ui/compositor/compositor.h"
  39. #include "ui/gfx/color_palette.h"
  40. #include "ui/gfx/geometry/insets.h"
  41. #include "ui/gfx/geometry/outsets.h"
  42. #include "ui/gfx/geometry/vector2d.h"
  43. #include "ui/gfx/shadow_value.h"
  44. #include "ui/views/background.h"
  45. #include "ui/views/border.h"
  46. #include "ui/views/controls/animated_image_view.h"
  47. #include "ui/views/controls/image_view.h"
  48. #include "ui/views/layout/box_layout_view.h"
  49. #include "ui/views/layout/fill_layout.h"
  50. #include "ui/views/widget/widget.h"
  51. namespace ash {
  52. namespace {
  53. // How often to shift the animation slightly to prevent screen burn.
  54. constexpr base::TimeDelta kAnimationJitterPeriod = base::Minutes(2);
  55. constexpr JitterCalculator::Config kAnimationJitterConfig = {
  56. /*step_size=*/2,
  57. /*x_min_translation=*/-10,
  58. /*x_max_translation=*/10,
  59. /*y_min_translation=*/-10,
  60. /*y_max_translation=*/10};
  61. constexpr base::TimeDelta kThroughputTrackerRestartPeriod = base::Seconds(30);
  62. // Amount of x and y padding there should be from the top-left of the
  63. // AmbientAnimationView to the top-left of the weather/time content views.
  64. constexpr int kWeatherTimeBorderPaddingDip = 28;
  65. // Amount of padding from the top-right of the AmbientAnimationView's
  66. // bounds to the top-right of the media string content views.
  67. constexpr int kMediaStringPaddingDip = 28;
  68. constexpr int kMediaStringTextElevation = 1;
  69. constexpr int kTimeFontSizeDip = 32;
  70. // Google Grey 500 with 10% opacity.
  71. constexpr SkColor kDarkModeShieldColor =
  72. SkColorSetA(gfx::kGoogleGrey900, SK_AlphaOPAQUE / 10);
  73. void LogCompositorThroughput(AmbientAnimationTheme theme, int smoothness) {
  74. // Use VLOG instead of DVLOG since this log is performance-related and
  75. // developers will almost certainly only care about this log on non-debug
  76. // builds.
  77. VLOG(1) << "Compositor throughput report: smoothness=" << smoothness;
  78. ambient::RecordAmbientModeAnimationSmoothness(smoothness, theme);
  79. }
  80. void OnCompositorThroughputReported(
  81. base::TimeTicks logging_start_time,
  82. AmbientAnimationTheme theme,
  83. const cc::FrameSequenceMetrics::CustomReportData& data) {
  84. base::TimeDelta duration = base::TimeTicks::Now() - logging_start_time;
  85. float duration_sec = duration.InSecondsF();
  86. VLOG(1) << "Compositor throughput report: frames_expected="
  87. << data.frames_expected << " frames_produced=" << data.frames_produced
  88. << " jank_count=" << data.jank_count
  89. << " expected_fps=" << data.frames_expected / duration_sec
  90. << " actual_fps=" << data.frames_produced / duration_sec
  91. << " duration=" << duration;
  92. metrics_util::ForSmoothness(
  93. base::BindRepeating(&LogCompositorThroughput, theme))
  94. .Run(data);
  95. }
  96. // Returns the maximum possible displacement in either dimension from the
  97. // original unshifted position when jitter is applied.
  98. int GetPaddingForAnimationJitter() {
  99. return std::max({abs(kAnimationJitterConfig.x_min_translation),
  100. abs(kAnimationJitterConfig.x_max_translation),
  101. abs(kAnimationJitterConfig.y_min_translation),
  102. abs(kAnimationJitterConfig.y_max_translation)});
  103. }
  104. // When text with shadows requires X pixels of padding from the edges of its
  105. // bounding view, it is not always sufficient to simply create a border within
  106. // the view that is X pixels wide. In the event that the text's shadow extends
  107. // past the text in a given direction, the text's shadow ends up with X pixels
  108. // of padding from the edge rather than the text itself.
  109. //
  110. // This returns the amount to *subtract* from each side of a text view's border
  111. // such that the text ultimately has X pixels of padding from the view's edge,
  112. // and the shadow may extend into the padding.
  113. gfx::Outsets GetTextShadowCorrection(const gfx::ShadowValues& text_shadows) {
  114. // A positive shadow outset means the shadow extends past the text in that
  115. // direction. A negative shadow outset means the shadow is "behind" the text
  116. // in that direction. In this case, subtracting the negative outset value
  117. // will result in padding that is too large (X + <shadow offset>). Hence,
  118. // impose a "floor" of 0 pixels here.
  119. static constexpr gfx::Outsets kZeroOutsetsFloor;
  120. gfx::Outsets shadow_outsets =
  121. gfx::ShadowValue::GetMargin(text_shadows).ToOutsets();
  122. shadow_outsets.SetToMax(kZeroOutsetsFloor);
  123. return shadow_outsets;
  124. }
  125. // The border serves as padding between the GlanceableInfoView and its
  126. // parent view's bounds.
  127. std::unique_ptr<views::Border> CreateGlanceableInfoBorder(
  128. const gfx::Vector2d& jitter = gfx::Vector2d()) {
  129. gfx::Outsets shadow_text_correction =
  130. GetTextShadowCorrection(ambient::util::GetTextShadowValues(nullptr));
  131. int top_padding =
  132. kWeatherTimeBorderPaddingDip - shadow_text_correction.top() + jitter.y();
  133. int left_padding =
  134. kWeatherTimeBorderPaddingDip - shadow_text_correction.left() + jitter.x();
  135. DCHECK_GE(top_padding, 0);
  136. DCHECK_GE(left_padding, 0);
  137. return views::CreateEmptyBorder(
  138. gfx::Insets::TLBR(top_padding, left_padding, 0, 0));
  139. }
  140. // The border serves as padding between the MediaStringView and its
  141. // parent view's bounds.
  142. std::unique_ptr<views::Border> CreateMediaStringBorder(
  143. const gfx::Vector2d& jitter = gfx::Vector2d()) {
  144. gfx::Outsets shadow_text_correction = GetTextShadowCorrection(
  145. ambient::util::GetTextShadowValues(nullptr, kMediaStringTextElevation));
  146. int top_padding =
  147. kMediaStringPaddingDip - shadow_text_correction.top() + jitter.y();
  148. int right_padding =
  149. kMediaStringPaddingDip - shadow_text_correction.right() + jitter.x();
  150. DCHECK_GE(top_padding, 0);
  151. DCHECK_GE(right_padding, 0);
  152. return views::CreateEmptyBorder(
  153. gfx::Insets::TLBR(top_padding, 0, 0, right_padding));
  154. }
  155. } // namespace
  156. AmbientAnimationView::AmbientAnimationView(
  157. AmbientViewDelegateImpl* view_delegate,
  158. AmbientAnimationProgressTracker* progress_tracker,
  159. std::unique_ptr<const AmbientAnimationStaticResources> static_resources,
  160. AmbientMultiScreenMetricsRecorder* multi_screen_metrics_recorder)
  161. : view_delegate_(view_delegate),
  162. progress_tracker_(progress_tracker),
  163. static_resources_(std::move(static_resources)),
  164. animation_photo_provider_(static_resources_.get(),
  165. view_delegate->GetAmbientBackendModel()),
  166. animation_jitter_calculator_(kAnimationJitterConfig) {
  167. DCHECK(view_delegate_);
  168. SetID(AmbientViewID::kAmbientAnimationView);
  169. Init(multi_screen_metrics_recorder);
  170. }
  171. AmbientAnimationView::~AmbientAnimationView() = default;
  172. void AmbientAnimationView::Init(
  173. AmbientMultiScreenMetricsRecorder* multi_screen_metrics_recorder) {
  174. SetUseDefaultFillLayout(true);
  175. views::View* animation_container_view =
  176. AddChildView(std::make_unique<views::View>());
  177. animation_container_view->SetUseDefaultFillLayout(true);
  178. // Purely for performance reasons. Gains 3-4 fps.
  179. animation_container_view->SetPaintToLayer();
  180. // In portrait mode, the landscape animation file is currently being used. Its
  181. // width is scaled down to match the width of the portrait screen, and it's
  182. // center-aligned leaving empty space on the top and bottom of the screen. To
  183. // make this look less obvious to the user, make the empty space exactly match
  184. // the background color of the animation itself. This may be removed in the
  185. // future if portrait versions of the animations are made.
  186. animation_container_view->SetBackground(views::CreateSolidBackground(
  187. GetAnimationBackgroundColor(*static_resources_->GetSkottieWrapper())));
  188. animated_image_view_ = animation_container_view->AddChildView(
  189. std::make_unique<views::AnimatedImageView>());
  190. auto animation = std::make_unique<lottie::Animation>(
  191. static_resources_->GetSkottieWrapper(), cc::SkottieColorMap(),
  192. &animation_photo_provider_);
  193. animation_observer_.Observe(animation.get());
  194. DCHECK(multi_screen_metrics_recorder);
  195. multi_screen_metrics_recorder->RegisterScreen(animation.get());
  196. animated_image_view_->SetAnimatedImage(std::move(animation));
  197. animated_image_view_observer_.Observe(animated_image_view_);
  198. animation_attribution_provider_ =
  199. std::make_unique<AmbientAnimationAttributionProvider>(
  200. &animation_photo_provider_, animated_image_view_->animated_image());
  201. // SetPaintToLayer() causes a view to be painted above its non-layer-backed
  202. // siblings, irrespective of the order they were added in. Using an
  203. // intermediate layer-backed |animation_container_view| ensures the shield is
  204. // painted on top of the animation, while still getting performance benefits.
  205. auto shield_view = std::make_unique<views::View>();
  206. shield_view->SetID(kAmbientShieldView);
  207. shield_view->SetBackground(
  208. views::CreateSolidBackground(kDarkModeShieldColor));
  209. shield_view_controller_ = std::make_unique<AmbientAnimationShieldController>(
  210. std::move(shield_view), /*parent_view=*/animation_container_view);
  211. // The set of weather/time views embedded within GlanceableInfoView should
  212. // appear in the top-left of the the AmbientAnimationView's boundaries with
  213. // |kWeatherTimeBorderPaddingDip| from the top-left corner. However, the
  214. // weather/time components must be bottom-aligned like so:
  215. // +-------------------------------------------------------------------------+
  216. // | |
  217. // | +----+ +--+ |
  218. // | | |+---+| | |
  219. // | | || || | |
  220. // | +----++---++--+ |
  221. // | |
  222. // | |
  223. // | |
  224. // | |
  225. // | |
  226. // | |
  227. // | |
  228. // | |
  229. // +-------------------------------------------------------------------------+
  230. // As opposed to top-aligned :
  231. // +-------------------------------------------------------------------------+
  232. // | |
  233. // | +----++---++--+ |
  234. // | | || || | |
  235. // | | |+---+| | |
  236. // | +----+ +--+ |
  237. // | |
  238. // | |
  239. // | |
  240. // | |
  241. // | |
  242. // | |
  243. // | |
  244. // | |
  245. // +-------------------------------------------------------------------------+
  246. //
  247. // To accomplish this, a "container" view is first created that is top-aligned
  248. // and has no actual content. GlanceableInfoView is then added as a child of
  249. // the container view and bottom-aligns its contents within the container.
  250. glanceable_info_container_ =
  251. AddChildView(std::make_unique<views::BoxLayoutView>());
  252. glanceable_info_container_->SetOrientation(
  253. views::BoxLayout::Orientation::kVertical);
  254. glanceable_info_container_->SetMainAxisAlignment(
  255. views::BoxLayout::MainAxisAlignment::kStart);
  256. glanceable_info_container_->SetCrossAxisAlignment(
  257. views::BoxLayout::CrossAxisAlignment::kStart);
  258. glanceable_info_container_->SetBorder(CreateGlanceableInfoBorder());
  259. glanceable_info_container_->AddChildView(std::make_unique<GlanceableInfoView>(
  260. view_delegate_.get(), kTimeFontSizeDip,
  261. /*time_temperature_font_color=*/gfx::kGoogleGrey900));
  262. // Media string should appear in the top-right corner of the
  263. // AmbientAnimationView's bounds.
  264. media_string_container_ =
  265. AddChildView(std::make_unique<views::BoxLayoutView>());
  266. media_string_container_->SetOrientation(
  267. views::BoxLayout::Orientation::kVertical);
  268. media_string_container_->SetMainAxisAlignment(
  269. views::BoxLayout::MainAxisAlignment::kStart);
  270. media_string_container_->SetCrossAxisAlignment(
  271. views::BoxLayout::CrossAxisAlignment::kEnd);
  272. media_string_container_->SetBorder(CreateMediaStringBorder());
  273. MediaStringView* media_string_view = media_string_container_->AddChildView(
  274. std::make_unique<MediaStringView>(MediaStringView::Settings(
  275. {/*icon_light_mode_color=*/gfx::kGoogleGrey600,
  276. /*icon_dark_mode_color=*/gfx::kGoogleGrey500,
  277. /*text_light_mode_color=*/gfx::kGoogleGrey600,
  278. /*text_dark_mode_color=*/gfx::kGoogleGrey500,
  279. kMediaStringTextElevation})));
  280. media_string_view->SetVisible(false);
  281. }
  282. void AmbientAnimationView::AnimationCycleEnded(
  283. const lottie::Animation* animation) {
  284. view_delegate_->NotifyObserversMarkerHit(
  285. AmbientPhotoConfig::Marker::kUiCycleEnded);
  286. base::TimeTicks now = base::TimeTicks::Now();
  287. if (now - last_jitter_timestamp_ >= kAnimationJitterPeriod) {
  288. // AnimationCycleEnded() may be called while a ui "paint" operation is still
  289. // in progress. Changing translation properties of the UI while a paint
  290. // operation is in progress results in a fatal error deep in the UI stack.
  291. // Thus, post a task to apply jitter rather than invoking it synchronously.
  292. base::SequencedTaskRunnerHandle::Get()->PostTask(
  293. FROM_HERE, base::BindOnce(&AmbientAnimationView::ApplyJitter,
  294. weak_factory_.GetWeakPtr()));
  295. last_jitter_timestamp_ = now;
  296. }
  297. }
  298. void AmbientAnimationView::OnViewBoundsChanged(View* observed_view) {
  299. DCHECK_EQ(observed_view, static_cast<View*>(animated_image_view_));
  300. gfx::Rect content_bounds = animated_image_view_->GetContentsBounds();
  301. DVLOG(4) << __func__ << " to " << content_bounds.ToString();
  302. if (content_bounds.IsEmpty())
  303. return;
  304. // By default, the |animated_image_view_| will render the animation with the
  305. // fixed dimensions specified in the Lottie file. To render the animation
  306. // at the view's full bounds, wait for the view's initial layout to happen
  307. // so that its proper bounds become available (they are 0x0 initially) before
  308. // starting the animation playback.
  309. gfx::Rect previous_animation_bounds = animated_image_view_->GetImageBounds();
  310. AmbientAnimationResizer::Resize(*animated_image_view_,
  311. GetPaddingForAnimationJitter());
  312. AmbientAnimationAttributionTransformer::TransformTextBox(
  313. *animated_image_view_);
  314. // When the device is in portrait mode, the landscape version of the
  315. // animation is currently being used. The tree shadow in "feel the breeze"
  316. // gets cut off at the top when doing this, making it look strange. UX
  317. // decision is to just omit the tree shadow in portrait mode. If/when
  318. // portrait versions of the animation are made, this logic can be removed.
  319. if (static_resources_->GetAmbientAnimationTheme() ==
  320. AmbientAnimationTheme::kFeelTheBreeze) {
  321. bool tree_shadow_toggled = animation_photo_provider_.ToggleStaticImageAsset(
  322. cc::HashSkottieResourceId(ambient::resources::kTreeShadowAssetId),
  323. /*enabled=*/content_bounds.width() >= content_bounds.height());
  324. DCHECK(tree_shadow_toggled);
  325. }
  326. DVLOG(4)
  327. << "View bounds available. Resized animation with native size "
  328. << animated_image_view_->animated_image()->GetOriginalSize().ToString()
  329. << " from " << previous_animation_bounds.ToString() << " to "
  330. << animated_image_view_->GetImageBounds().ToString();
  331. StartPlayingAnimation();
  332. if (!throughput_tracker_restart_timer_.IsRunning()) {
  333. RestartThroughputTracking();
  334. throughput_tracker_restart_timer_.Start(
  335. FROM_HERE, kThroughputTrackerRestartPeriod, this,
  336. &AmbientAnimationView::RestartThroughputTracking);
  337. }
  338. }
  339. void AmbientAnimationView::StartPlayingAnimation() {
  340. // There should only be one active AmbientAnimationPlayer at any given time,
  341. // otherwise multiple active players can lead to confusing simultaneous state
  342. // changes. So destroy the existing player first before creating a new one.
  343. animation_player_.reset();
  344. // |animated_image_view_| is owned by the base |View| class and outlives the
  345. // |animation_player_|, so it's safe to pass a raw ptr here.
  346. animation_player_ = std::make_unique<AmbientAnimationPlayer>(
  347. animated_image_view_, progress_tracker_.get());
  348. view_delegate_->NotifyObserversMarkerHit(
  349. AmbientPhotoConfig::Marker::kUiStartRendering);
  350. last_jitter_timestamp_ = base::TimeTicks::Now();
  351. }
  352. void AmbientAnimationView::RestartThroughputTracking() {
  353. // Stop() must be called to trigger throughput reporting.
  354. if (throughput_tracker_ && !throughput_tracker_->Stop()) {
  355. LOG(WARNING) << "Throughput will not be reported";
  356. }
  357. views::Widget* widget = GetWidget();
  358. DCHECK(widget);
  359. ui::Compositor* compositor = widget->GetCompositor();
  360. DCHECK(compositor);
  361. throughput_tracker_ = compositor->RequestNewThroughputTracker();
  362. throughput_tracker_->Start(
  363. base::BindOnce(&OnCompositorThroughputReported,
  364. /*logging_start_time=*/base::TimeTicks::Now(),
  365. static_resources_->GetAmbientAnimationTheme()));
  366. }
  367. void AmbientAnimationView::ApplyJitter() {
  368. gfx::Vector2d jitter = animation_jitter_calculator_.Calculate();
  369. DVLOG(4) << "Applying jitter to animation: " << jitter.ToString();
  370. // Sharing the same jitter between the animation and other peripheral content
  371. // keeps the spacing between features consistent.
  372. animated_image_view_->SetAdditionalTranslation(jitter);
  373. glanceable_info_container_->SetBorder(CreateGlanceableInfoBorder(jitter));
  374. media_string_container_->SetBorder(CreateMediaStringBorder(jitter));
  375. }
  376. BEGIN_METADATA(AmbientAnimationView, views::View)
  377. END_METADATA
  378. } // namespace ash