phone_status_view.cc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  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. #include "ash/system/phonehub/phone_status_view.h"
  5. #include <string>
  6. #include "ash/constants/ash_features.h"
  7. #include "ash/public/cpp/network_icon_image_source.h"
  8. #include "ash/public/cpp/shelf_config.h"
  9. #include "ash/resources/vector_icons/vector_icons.h"
  10. #include "ash/root_window_controller.h"
  11. #include "ash/strings/grit/ash_strings.h"
  12. #include "ash/style/ash_color_id.h"
  13. #include "ash/style/ash_color_provider.h"
  14. #include "ash/style/icon_button.h"
  15. #include "ash/system/phonehub/phone_hub_tray.h"
  16. #include "ash/system/phonehub/phone_hub_view_ids.h"
  17. #include "ash/system/status_area_widget.h"
  18. #include "ash/system/tray/tray_constants.h"
  19. #include "ash/system/tray/tray_popup_utils.h"
  20. #include "base/bind.h"
  21. #include "base/i18n/number_formatting.h"
  22. #include "base/strings/strcat.h"
  23. #include "base/strings/string_number_conversions.h"
  24. #include "ui/base/l10n/l10n_util.h"
  25. #include "ui/color/color_id.h"
  26. #include "ui/compositor/layer.h"
  27. #include "ui/gfx/color_utils.h"
  28. #include "ui/gfx/geometry/insets.h"
  29. #include "ui/gfx/image/image_skia.h"
  30. #include "ui/gfx/paint_vector_icon.h"
  31. #include "ui/gfx/text_elider.h"
  32. #include "ui/views/border.h"
  33. #include "ui/views/controls/image_view.h"
  34. #include "ui/views/controls/label.h"
  35. #include "ui/views/controls/separator.h"
  36. #include "ui/views/layout/box_layout.h"
  37. namespace ash {
  38. namespace {
  39. using PhoneStatusModel = phonehub::PhoneStatusModel;
  40. // Appearance in Dip.
  41. constexpr int kTitleContainerSpacing = 16;
  42. constexpr int kStatusSpacing = 4;
  43. constexpr gfx::Size kStatusIconSize(kUnifiedTrayIconSize, kUnifiedTrayIconSize);
  44. constexpr gfx::Size kSignalIconSize(15, 15);
  45. constexpr int kSeparatorHeight = 18;
  46. constexpr int kPhoneNameLabelWidthMax = 160;
  47. constexpr auto kBorderInsets = gfx::Insets::VH(0, 16);
  48. constexpr auto kBatteryLabelBorderInsets = gfx::Insets::TLBR(0, 0, 0, 4);
  49. // Typograph in dip.
  50. constexpr int kBatteryLabelFontSize = 11;
  51. // Multiplied by the int returned by GetSignalStrengthAsInt() to obtain a
  52. // percentage for the signal strength displayed by the tooltip when hovering
  53. // over the signal strength icon, and verbalized by ChromeVox.
  54. constexpr int kSignalStrengthToPercentageMultiplier = 25;
  55. int GetSignalStrengthAsInt(PhoneStatusModel::SignalStrength signal_strength) {
  56. switch (signal_strength) {
  57. case PhoneStatusModel::SignalStrength::kZeroBars:
  58. return 0;
  59. case PhoneStatusModel::SignalStrength::kOneBar:
  60. return 1;
  61. case PhoneStatusModel::SignalStrength::kTwoBars:
  62. return 2;
  63. case PhoneStatusModel::SignalStrength::kThreeBars:
  64. return 3;
  65. case PhoneStatusModel::SignalStrength::kFourBars:
  66. return 4;
  67. }
  68. }
  69. bool IsBatterySaverModeOn(const PhoneStatusModel& phone_status) {
  70. return phone_status.battery_saver_state() ==
  71. PhoneStatusModel::BatterySaverState::kOn;
  72. }
  73. } // namespace
  74. PhoneStatusView::PhoneStatusView(phonehub::PhoneModel* phone_model,
  75. Delegate* delegate)
  76. : TriView(kTitleContainerSpacing),
  77. phone_model_(phone_model),
  78. phone_name_label_(new views::Label),
  79. signal_icon_(new views::ImageView),
  80. battery_icon_(new views::ImageView),
  81. battery_label_(new views::Label) {
  82. DCHECK(delegate);
  83. // In dark light mode, we switch TrayBubbleView to use a textured layer
  84. // instead of solid color layer, so no need to create an extra layer here.
  85. if (!features::IsDarkLightModeEnabled()) {
  86. SetPaintToLayer();
  87. layer()->SetFillsBoundsOpaquely(false);
  88. }
  89. SetID(PhoneHubViewID::kPhoneStatusView);
  90. SetBorder(views::CreateEmptyBorder(kBorderInsets));
  91. // Phone name is placed at START container, Settings icon is
  92. // placed at END container, other phone states, i.e. battery level,
  93. // and Separator are placed at CENTER container.
  94. ConfigureTriViewContainer(TriView::Container::START);
  95. ConfigureTriViewContainer(TriView::Container::CENTER);
  96. ConfigureTriViewContainer(TriView::Container::END);
  97. phone_model_->AddObserver(this);
  98. phone_name_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
  99. phone_name_label_->SetEnabledColor(
  100. AshColorProvider::Get()->GetContentLayerColor(
  101. AshColorProvider::ContentLayerType::kTextColorPrimary));
  102. TrayPopupUtils::SetLabelFontList(phone_name_label_,
  103. TrayPopupUtils::FontStyle::kSubHeader);
  104. phone_name_label_->SetElideBehavior(gfx::ElideBehavior::ELIDE_TAIL);
  105. AddView(TriView::Container::START, phone_name_label_);
  106. AddView(TriView::Container::CENTER, signal_icon_);
  107. if (features::IsDarkLightModeEnabled()) {
  108. // The battery icon requires its own layer to properly render the masked
  109. // outline of the badge within the battery icon.
  110. battery_icon_->SetPaintToLayer();
  111. battery_icon_->layer()->SetFillsBoundsOpaquely(false);
  112. }
  113. AddView(TriView::Container::CENTER, battery_icon_);
  114. battery_label_->SetAutoColorReadabilityEnabled(false);
  115. battery_label_->SetSubpixelRenderingEnabled(false);
  116. battery_label_->SetEnabledColor(AshColorProvider::Get()->GetContentLayerColor(
  117. AshColorProvider::ContentLayerType::kTextColorPrimary));
  118. auto default_font = battery_label_->font_list();
  119. battery_label_->SetFontList(default_font.DeriveWithSizeDelta(
  120. kBatteryLabelFontSize - default_font.GetFontSize()));
  121. battery_label_->SetBorder(
  122. views::CreateEmptyBorder(kBatteryLabelBorderInsets));
  123. AddView(TriView::Container::CENTER, battery_label_);
  124. separator_ = new views::Separator();
  125. separator_->SetColorId(ui::kColorAshSystemUIMenuSeparator);
  126. separator_->SetPreferredLength(kSeparatorHeight);
  127. AddView(TriView::Container::CENTER, separator_);
  128. settings_button_ = new IconButton(
  129. base::BindRepeating(&Delegate::OpenConnectedDevicesSettings,
  130. base::Unretained(delegate)),
  131. IconButton::Type::kSmall, &kSystemMenuSettingsIcon,
  132. IDS_ASH_PHONE_HUB_CONNECTED_DEVICE_SETTINGS_LABEL);
  133. AddView(TriView::Container::END, settings_button_);
  134. separator_->SetVisible(delegate->CanOpenConnectedDeviceSettings());
  135. settings_button_->SetVisible(delegate->CanOpenConnectedDeviceSettings());
  136. }
  137. PhoneStatusView::~PhoneStatusView() {
  138. phone_model_->RemoveObserver(this);
  139. }
  140. void PhoneStatusView::OnThemeChanged() {
  141. TriView::OnThemeChanged();
  142. Update();
  143. }
  144. void PhoneStatusView::OnModelChanged() {
  145. Update();
  146. }
  147. void PhoneStatusView::Update() {
  148. // Set phone name text and elide it if needed.
  149. phone_name_label_->SetText(
  150. gfx::ElideText(phone_model_->phone_name().value_or(std::u16string()),
  151. phone_name_label_->font_list(), kPhoneNameLabelWidthMax,
  152. gfx::ELIDE_TAIL));
  153. // Clear the phone status if the status model returns null when the phone is
  154. // disconnected.
  155. if (!phone_model_->phone_status_model()) {
  156. ClearExistingStatus();
  157. // Hide separator if there is no preceding content.
  158. separator_->SetVisible(false);
  159. return;
  160. }
  161. UpdateMobileStatus();
  162. UpdateBatteryStatus();
  163. }
  164. void PhoneStatusView::UpdateMobileStatus() {
  165. const PhoneStatusModel& phone_status =
  166. phone_model_->phone_status_model().value();
  167. const SkColor primary_color = AshColorProvider::Get()->GetContentLayerColor(
  168. AshColorProvider::ContentLayerType::kIconColorPrimary);
  169. gfx::ImageSkia signal_image;
  170. std::u16string tooltip_text;
  171. switch (phone_status.mobile_status()) {
  172. case PhoneStatusModel::MobileStatus::kNoSim:
  173. signal_image = CreateVectorIcon(kPhoneHubMobileNoSimIcon, primary_color);
  174. tooltip_text =
  175. l10n_util::GetStringUTF16(IDS_ASH_PHONE_HUB_MOBILE_STATUS_NO_SIM);
  176. signal_icon_->SetImageSize(kStatusIconSize);
  177. break;
  178. case PhoneStatusModel::MobileStatus::kSimButNoReception:
  179. signal_image =
  180. CreateVectorIcon(kPhoneHubMobileNoConnectionIcon, primary_color);
  181. tooltip_text =
  182. l10n_util::GetStringUTF16(IDS_ASH_PHONE_HUB_MOBILE_STATUS_NO_NETWORK);
  183. signal_icon_->SetImageSize(kStatusIconSize);
  184. break;
  185. case PhoneStatusModel::MobileStatus::kSimWithReception:
  186. const PhoneStatusModel::MobileConnectionMetadata& metadata =
  187. phone_status.mobile_connection_metadata().value();
  188. int signal_strength = GetSignalStrengthAsInt(metadata.signal_strength);
  189. signal_image = gfx::CanvasImageSource::MakeImageSkia<
  190. network_icon::SignalStrengthImageSource>(
  191. network_icon::ImageType::BARS, primary_color, kSignalIconSize,
  192. signal_strength);
  193. signal_icon_->SetImageSize(kSignalIconSize);
  194. tooltip_text = l10n_util::GetStringFUTF16(
  195. IDS_ASH_PHONE_HUB_MOBILE_STATUS_NETWORK_NAME_AND_STRENGTH,
  196. metadata.mobile_provider,
  197. base::NumberToString16(signal_strength *
  198. kSignalStrengthToPercentageMultiplier));
  199. break;
  200. }
  201. signal_icon_->SetImage(signal_image);
  202. signal_icon_->SetTooltipText(tooltip_text);
  203. }
  204. void PhoneStatusView::UpdateBatteryStatus() {
  205. const PhoneStatusModel& phone_status =
  206. phone_model_->phone_status_model().value();
  207. const PowerStatus::BatteryImageInfo& info = CalculateBatteryInfo();
  208. const SkColor icon_bg_color = color_utils::GetResultingPaintColor(
  209. ShelfConfig::Get()->GetShelfControlButtonColor(),
  210. GetColorProvider()->GetColor(kColorAshShieldAndBaseOpaque));
  211. const SkColor icon_fg_color = AshColorProvider::Get()->GetContentLayerColor(
  212. IsBatterySaverModeOn(phone_status)
  213. ? AshColorProvider::ContentLayerType::kIconColorWarning
  214. : AshColorProvider::ContentLayerType::kIconColorPrimary);
  215. battery_icon_->SetImage(PowerStatus::GetBatteryImage(
  216. info, kUnifiedTrayBatteryIconSize, icon_bg_color, icon_fg_color));
  217. SetBatteryTooltipText();
  218. battery_label_->SetText(
  219. base::FormatPercent(phone_status.battery_percentage()));
  220. battery_label_->SetAccessibleName(l10n_util::GetStringFUTF16(
  221. IDS_ASH_PHONE_HUB_BATTERY_PERCENTAGE_ACCESSIBLE_TEXT,
  222. base::NumberToString16(phone_status.battery_percentage())));
  223. }
  224. PowerStatus::BatteryImageInfo PhoneStatusView::CalculateBatteryInfo() {
  225. PowerStatus::BatteryImageInfo info;
  226. const PhoneStatusModel& phone_status =
  227. phone_model_->phone_status_model().value();
  228. info.charge_percent = phone_status.battery_percentage();
  229. if (IsBatterySaverModeOn(phone_status)) {
  230. info.icon_badge = &kPhoneHubBatterySaverIcon;
  231. if (features::IsDarkLightModeEnabled()) {
  232. info.badge_outline = &kPhoneHubBatterySaverOutlineMaskIcon;
  233. } else {
  234. info.badge_outline = &kPhoneHubBatterySaverOutlineIcon;
  235. }
  236. return info;
  237. }
  238. switch (phone_status.charging_state()) {
  239. case PhoneStatusModel::ChargingState::kNotCharging:
  240. info.alert_if_low = true;
  241. if (info.charge_percent < PowerStatus::kCriticalBatteryChargePercentage) {
  242. info.icon_badge = &kUnifiedMenuBatteryAlertIcon;
  243. if (features::IsDarkLightModeEnabled()) {
  244. info.badge_outline = &kUnifiedMenuBatteryAlertOutlineMaskIcon;
  245. } else {
  246. info.badge_outline = &kUnifiedMenuBatteryAlertOutlineIcon;
  247. }
  248. }
  249. break;
  250. case PhoneStatusModel::ChargingState::kChargingAc:
  251. info.icon_badge = &kUnifiedMenuBatteryBoltIcon;
  252. if (features::IsDarkLightModeEnabled()) {
  253. info.badge_outline = &kUnifiedMenuBatteryBoltOutlineMaskIcon;
  254. } else {
  255. info.badge_outline = &kUnifiedMenuBatteryBoltOutlineIcon;
  256. }
  257. break;
  258. case PhoneStatusModel::ChargingState::kChargingUsb:
  259. info.icon_badge = &kUnifiedMenuBatteryUnreliableIcon;
  260. if (features::IsDarkLightModeEnabled()) {
  261. info.badge_outline = &kUnifiedMenuBatteryUnreliableOutlineMaskIcon;
  262. } else {
  263. info.badge_outline = &kUnifiedMenuBatteryUnreliableOutlineIcon;
  264. }
  265. break;
  266. }
  267. return info;
  268. }
  269. void PhoneStatusView::SetBatteryTooltipText() {
  270. const PhoneStatusModel& phone_status =
  271. phone_model_->phone_status_model().value();
  272. int charging_tooltip_id;
  273. switch (phone_status.charging_state()) {
  274. case PhoneStatusModel::ChargingState::kNotCharging:
  275. charging_tooltip_id = IDS_ASH_PHONE_HUB_BATTERY_STATUS_NOT_CHARGING;
  276. break;
  277. case PhoneStatusModel::ChargingState::kChargingAc:
  278. charging_tooltip_id = IDS_ASH_PHONE_HUB_BATTERY_STATUS_CHARGING_AC;
  279. break;
  280. case PhoneStatusModel::ChargingState::kChargingUsb:
  281. charging_tooltip_id = IDS_ASH_PHONE_HUB_BATTERY_STATUS_CHARGING_USB;
  282. break;
  283. }
  284. std::u16string charging_tooltip =
  285. l10n_util::GetStringUTF16(charging_tooltip_id);
  286. bool battery_saver_on = phone_status.battery_saver_state() ==
  287. PhoneStatusModel::BatterySaverState::kOn;
  288. std::u16string batter_saver_tooltip =
  289. battery_saver_on
  290. ? l10n_util::GetStringUTF16(IDS_ASH_PHONE_HUB_BATTERY_SAVER_ON)
  291. : l10n_util::GetStringUTF16(IDS_ASH_PHONE_HUB_BATTERY_SAVER_OFF);
  292. battery_icon_->SetTooltipText(
  293. l10n_util::GetStringFUTF16(IDS_ASH_PHONE_HUB_BATTERY_TOOLTIP,
  294. charging_tooltip, batter_saver_tooltip));
  295. }
  296. void PhoneStatusView::ClearExistingStatus() {
  297. // Clear mobile status.
  298. signal_icon_->SetImage(gfx::ImageSkia());
  299. // Clear battery status.
  300. battery_icon_->SetImage(gfx::ImageSkia());
  301. battery_label_->SetText(std::u16string());
  302. }
  303. void PhoneStatusView::ConfigureTriViewContainer(TriView::Container container) {
  304. std::unique_ptr<views::BoxLayout> layout;
  305. switch (container) {
  306. case TriView::Container::START:
  307. SetFlexForContainer(TriView::Container::START, 1.f);
  308. layout = std::make_unique<views::BoxLayout>(
  309. views::BoxLayout::Orientation::kVertical);
  310. layout->set_main_axis_alignment(
  311. views::BoxLayout::MainAxisAlignment::kCenter);
  312. layout->set_cross_axis_alignment(
  313. views::BoxLayout::CrossAxisAlignment::kStretch);
  314. break;
  315. case TriView::Container::CENTER:
  316. layout = std::make_unique<views::BoxLayout>(
  317. views::BoxLayout::Orientation::kHorizontal, gfx::Insets(),
  318. kStatusSpacing);
  319. layout->set_main_axis_alignment(
  320. views::BoxLayout::MainAxisAlignment::kEnd);
  321. layout->set_cross_axis_alignment(
  322. views::BoxLayout::CrossAxisAlignment::kCenter);
  323. break;
  324. case TriView::Container::END:
  325. layout = std::make_unique<views::BoxLayout>(
  326. views::BoxLayout::Orientation::kHorizontal);
  327. layout->set_main_axis_alignment(
  328. views::BoxLayout::MainAxisAlignment::kCenter);
  329. layout->set_cross_axis_alignment(
  330. views::BoxLayout::CrossAxisAlignment::kCenter);
  331. break;
  332. }
  333. SetContainerLayout(container, std::move(layout));
  334. SetMinSize(container, gfx::Size(0, kUnifiedDetailedViewTitleRowHeight));
  335. }
  336. } // namespace ash