[go: nahoru, domu]

blob: 1776342d9db9a702284dd4202b6b12f424acaf17 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/system/phonehub/phone_status_view.h"
#include <string>
#include "ash/public/cpp/network_icon_image_source.h"
#include "ash/public/cpp/shelf_config.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/root_window_controller.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_provider.h"
#include "ash/system/phonehub/phone_hub_tray.h"
#include "ash/system/phonehub/phone_hub_view_ids.h"
#include "ash/system/power/battery_image_source.h"
#include "ash/system/status_area_widget.h"
#include "ash/system/tray/tray_constants.h"
#include "ash/system/tray/tray_popup_utils.h"
#include "base/i18n/number_formatting.h"
#include "base/strings/strcat.h"
#include "base/strings/string16.h"
#include "base/strings/string_number_conversions.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/gfx/text_elider.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/separator.h"
#include "ui/views/layout/box_layout.h"
namespace ash {
using PhoneStatusModel = chromeos::phonehub::PhoneStatusModel;
namespace {
// Appearance in Dip.
constexpr int kTitleContainerSpacing = 16;
constexpr int kStatusSpacing = 4;
constexpr gfx::Size kStatusIconSize(kUnifiedTrayIconSize, kUnifiedTrayIconSize);
constexpr gfx::Size kSignalIconSize(15, 15);
constexpr int kSeparatorHeight = 18;
constexpr int kPhoneNameLabelWidthMax = 160;
constexpr gfx::Insets kBorderInsets(0, 16);
constexpr gfx::Insets kBatteryLabelBorderInsets(0, 0, 0, 4);
// Typograph in dip.
constexpr int kBatteryLabelFontSize = 11;
// Multiplied by the int returned by GetSignalStrengthAsInt() to obtain a
// percentage for the signal strength displayed by the tooltip when hovering
// over the signal strength icon, and verbalized by ChromeVox.
constexpr int kSignalStrengthToPercentageMultiplier = 25;
int GetSignalStrengthAsInt(PhoneStatusModel::SignalStrength signal_strength) {
switch (signal_strength) {
case PhoneStatusModel::SignalStrength::kZeroBars:
return 0;
case PhoneStatusModel::SignalStrength::kOneBar:
return 1;
case PhoneStatusModel::SignalStrength::kTwoBars:
return 2;
case PhoneStatusModel::SignalStrength::kThreeBars:
return 3;
case PhoneStatusModel::SignalStrength::kFourBars:
return 4;
}
}
// ImageSource for the battery icon.
class PhoneHubBatteryImageSource : public BatteryImageSource {
public:
PhoneHubBatteryImageSource(const PowerStatus::BatteryImageInfo& info,
int height,
SkColor bg_color,
SkColor fg_color,
bool in_battery_saver_mode)
: BatteryImageSource(info, height, bg_color, fg_color),
bg_color_(bg_color),
in_battery_saver_mode_(in_battery_saver_mode) {}
~PhoneHubBatteryImageSource() override = default;
// BatteryImageSource:
void Draw(gfx::Canvas* canvas) override {
BatteryImageSource::Draw(canvas);
if (!in_battery_saver_mode_)
return;
SkColor saver_color = AshColorProvider::Get()->GetContentLayerColor(
AshColorProvider::ContentLayerType::kIconColorWarning);
gfx::ImageSkia icon = CreateVectorIcon(kBatteryIcon, saver_color);
// Draw the solid outline of the battery icon.
canvas->DrawImageInt(icon, 0, 0);
PaintVectorIcon(canvas, kPhoneHubBatterySaverOutlineIcon, bg_color_);
PaintVectorIcon(canvas, kPhoneHubBatterySaverIcon, saver_color);
}
private:
const SkColor bg_color_;
bool in_battery_saver_mode_ = false;
};
} // namespace
PhoneStatusView::PhoneStatusView(chromeos::phonehub::PhoneModel* phone_model,
Delegate* delegate)
: TriView(kTitleContainerSpacing),
phone_model_(phone_model),
phone_name_label_(new views::Label),
signal_icon_(new views::ImageView),
battery_icon_(new views::ImageView),
battery_label_(new views::Label) {
DCHECK(delegate);
SetPaintToLayer();
layer()->SetFillsBoundsOpaquely(false);
SetID(PhoneHubViewID::kPhoneStatusView);
SetBorder(views::CreateEmptyBorder(kBorderInsets));
// Phone name is placed at START container, Settings icon is
// placed at END container, other phone states, i.e. battery level,
// and Separator are placed at CENTER container.
ConfigureTriViewContainer(TriView::Container::START);
ConfigureTriViewContainer(TriView::Container::CENTER);
ConfigureTriViewContainer(TriView::Container::END);
phone_model_->AddObserver(this);
phone_name_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
phone_name_label_->SetEnabledColor(
AshColorProvider::Get()->GetContentLayerColor(
AshColorProvider::ContentLayerType::kTextColorPrimary));
TrayPopupUtils::SetLabelFontList(phone_name_label_,
TrayPopupUtils::FontStyle::kSubHeader);
phone_name_label_->SetElideBehavior(gfx::ElideBehavior::ELIDE_TAIL);
AddView(TriView::Container::START, phone_name_label_);
AddView(TriView::Container::CENTER, signal_icon_);
AddView(TriView::Container::CENTER, battery_icon_);
battery_label_->SetAutoColorReadabilityEnabled(false);
battery_label_->SetSubpixelRenderingEnabled(false);
battery_label_->SetEnabledColor(AshColorProvider::Get()->GetContentLayerColor(
AshColorProvider::ContentLayerType::kTextColorPrimary));
auto default_font = battery_label_->font_list();
battery_label_->SetFontList(default_font.DeriveWithSizeDelta(
kBatteryLabelFontSize - default_font.GetFontSize()));
battery_label_->SetBorder(
views::CreateEmptyBorder(kBatteryLabelBorderInsets));
AddView(TriView::Container::CENTER, battery_label_);
separator_ = new views::Separator();
separator_->SetColor(AshColorProvider::Get()->GetContentLayerColor(
AshColorProvider::ContentLayerType::kSeparatorColor));
separator_->SetPreferredHeight(kSeparatorHeight);
AddView(TriView::Container::CENTER, separator_);
settings_button_ = new TopShortcutButton(
base::BindRepeating(&Delegate::OpenConnectedDevicesSettings,
base::Unretained(delegate)),
kSystemMenuSettingsIcon,
IDS_ASH_PHONE_HUB_CONNECTED_DEVICE_SETTINGS_LABEL);
AddView(TriView::Container::END, settings_button_);
separator_->SetVisible(delegate->CanOpenConnectedDeviceSettings());
settings_button_->SetVisible(delegate->CanOpenConnectedDeviceSettings());
Update();
}
PhoneStatusView::~PhoneStatusView() {
phone_model_->RemoveObserver(this);
}
void PhoneStatusView::OnModelChanged() {
Update();
}
void PhoneStatusView::Update() {
// Set phone name text and elide it if needed.
phone_name_label_->SetText(
gfx::ElideText(phone_model_->phone_name().value_or(std::u16string()),
phone_name_label_->font_list(), kPhoneNameLabelWidthMax,
gfx::ELIDE_TAIL));
// Clear the phone status if the status model returns null when the phone is
// disconnected.
if (!phone_model_->phone_status_model()) {
ClearExistingStatus();
// Hide separator if there is no preceding content.
separator_->SetVisible(false);
return;
}
UpdateMobileStatus();
UpdateBatteryStatus();
}
void PhoneStatusView::UpdateMobileStatus() {
const PhoneStatusModel& phone_status =
phone_model_->phone_status_model().value();
const SkColor primary_color = AshColorProvider::Get()->GetContentLayerColor(
AshColorProvider::ContentLayerType::kIconColorPrimary);
gfx::ImageSkia signal_image;
std::u16string tooltip_text;
switch (phone_status.mobile_status()) {
case PhoneStatusModel::MobileStatus::kNoSim:
signal_image = CreateVectorIcon(kPhoneHubMobileNoSimIcon, primary_color);
tooltip_text =
l10n_util::GetStringUTF16(IDS_ASH_PHONE_HUB_MOBILE_STATUS_NO_SIM);
signal_icon_->SetImageSize(kStatusIconSize);
break;
case PhoneStatusModel::MobileStatus::kSimButNoReception:
signal_image =
CreateVectorIcon(kPhoneHubMobileNoConnectionIcon, primary_color);
tooltip_text =
l10n_util::GetStringUTF16(IDS_ASH_PHONE_HUB_MOBILE_STATUS_NO_NETWORK);
signal_icon_->SetImageSize(kStatusIconSize);
break;
case PhoneStatusModel::MobileStatus::kSimWithReception:
const PhoneStatusModel::MobileConnectionMetadata& metadata =
phone_status.mobile_connection_metadata().value();
int signal_strength = GetSignalStrengthAsInt(metadata.signal_strength);
signal_image = gfx::CanvasImageSource::MakeImageSkia<
network_icon::SignalStrengthImageSource>(
network_icon::ImageType::BARS, primary_color, kSignalIconSize,
signal_strength);
signal_icon_->SetImageSize(kSignalIconSize);
tooltip_text = l10n_util::GetStringFUTF16(
IDS_ASH_PHONE_HUB_MOBILE_STATUS_NETWORK_NAME_AND_STRENGTH,
metadata.mobile_provider,
base::NumberToString16(signal_strength *
kSignalStrengthToPercentageMultiplier));
break;
}
signal_icon_->SetImage(signal_image);
signal_icon_->SetTooltipText(tooltip_text);
}
void PhoneStatusView::UpdateBatteryStatus() {
const PhoneStatusModel& phone_status =
phone_model_->phone_status_model().value();
const PowerStatus::BatteryImageInfo& info = CalculateBatteryInfo();
const SkColor icon_bg_color = color_utils::GetResultingPaintColor(
ShelfConfig::Get()->GetShelfControlButtonColor(),
AshColorProvider::Get()->GetBackgroundColor());
const SkColor icon_fg_color = AshColorProvider::Get()->GetContentLayerColor(
AshColorProvider::ContentLayerType::kIconColorPrimary);
bool in_battery_saver_mode = phone_status.battery_saver_state() ==
PhoneStatusModel::BatterySaverState::kOn;
auto* source = new PhoneHubBatteryImageSource(info, kStatusIconSize.height(),
icon_bg_color, icon_fg_color,
in_battery_saver_mode);
battery_icon_->SetImage(
gfx::ImageSkia(base::WrapUnique(source), source->size()));
SetBatteryTooltipText();
battery_label_->SetText(
base::FormatPercent(phone_status.battery_percentage()));
battery_label_->SetAccessibleName(l10n_util::GetStringFUTF16(
IDS_ASH_PHONE_HUB_BATTERY_PERCENTAGE_ACCESSIBLE_TEXT,
base::NumberToString16(phone_status.battery_percentage())));
}
PowerStatus::BatteryImageInfo PhoneStatusView::CalculateBatteryInfo() {
PowerStatus::BatteryImageInfo info;
const PhoneStatusModel& phone_status =
phone_model_->phone_status_model().value();
info.charge_percent = phone_status.battery_percentage();
switch (phone_status.charging_state()) {
case PhoneStatusModel::ChargingState::kNotCharging:
info.alert_if_low = true;
if (info.charge_percent < PowerStatus::kCriticalBatteryChargePercentage) {
info.icon_badge = &kUnifiedMenuBatteryAlertIcon;
info.badge_outline = &kUnifiedMenuBatteryAlertOutlineIcon;
}
break;
case PhoneStatusModel::ChargingState::kChargingAc:
info.icon_badge = &kUnifiedMenuBatteryBoltIcon;
info.badge_outline = &kUnifiedMenuBatteryBoltOutlineIcon;
break;
case PhoneStatusModel::ChargingState::kChargingUsb:
info.icon_badge = &kUnifiedMenuBatteryUnreliableIcon;
info.badge_outline = &kUnifiedMenuBatteryUnreliableOutlineIcon;
break;
}
return info;
}
void PhoneStatusView::SetBatteryTooltipText() {
const PhoneStatusModel& phone_status =
phone_model_->phone_status_model().value();
int charging_tooltip_id;
switch (phone_status.charging_state()) {
case PhoneStatusModel::ChargingState::kNotCharging:
charging_tooltip_id = IDS_ASH_PHONE_HUB_BATTERY_STATUS_NOT_CHARGING;
break;
case PhoneStatusModel::ChargingState::kChargingAc:
charging_tooltip_id = IDS_ASH_PHONE_HUB_BATTERY_STATUS_CHARGING_AC;
break;
case PhoneStatusModel::ChargingState::kChargingUsb:
charging_tooltip_id = IDS_ASH_PHONE_HUB_BATTERY_STATUS_CHARGING_USB;
break;
}
std::u16string charging_tooltip =
l10n_util::GetStringUTF16(charging_tooltip_id);
bool battery_saver_on = phone_status.battery_saver_state() ==
PhoneStatusModel::BatterySaverState::kOn;
std::u16string batter_saver_tooltip =
battery_saver_on
? l10n_util::GetStringUTF16(IDS_ASH_PHONE_HUB_BATTERY_SAVER_ON)
: l10n_util::GetStringUTF16(IDS_ASH_PHONE_HUB_BATTERY_SAVER_OFF);
battery_icon_->SetTooltipText(
l10n_util::GetStringFUTF16(IDS_ASH_PHONE_HUB_BATTERY_TOOLTIP,
charging_tooltip, batter_saver_tooltip));
}
void PhoneStatusView::ClearExistingStatus() {
// Clear mobile status.
signal_icon_->SetImage(gfx::ImageSkia());
// Clear battery status.
battery_icon_->SetImage(gfx::ImageSkia());
battery_label_->SetText(std::u16string());
}
void PhoneStatusView::ConfigureTriViewContainer(TriView::Container container) {
std::unique_ptr<views::BoxLayout> layout;
switch (container) {
case TriView::Container::START:
SetFlexForContainer(TriView::Container::START, 1.f);
layout = std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical);
layout->set_main_axis_alignment(
views::BoxLayout::MainAxisAlignment::kCenter);
layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kStretch);
break;
case TriView::Container::CENTER:
layout = std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal, gfx::Insets(),
kStatusSpacing);
layout->set_main_axis_alignment(
views::BoxLayout::MainAxisAlignment::kEnd);
layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kCenter);
break;
case TriView::Container::END:
layout = std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal);
layout->set_main_axis_alignment(
views::BoxLayout::MainAxisAlignment::kCenter);
layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kCenter);
break;
}
SetContainerLayout(container, std::move(layout));
SetMinSize(container, gfx::Size(0, kUnifiedDetailedViewTitleRowHeight));
}
} // namespace ash