blob: 7bcf0a782917e829376a6635693f679e2056ddb5 [file]
// 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 "chromeos/components/phonehub/phone_status_processor.h"
#include "base/containers/flat_set.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "chromeos/components/phonehub/do_not_disturb_controller.h"
#include "chromeos/components/phonehub/find_my_device_controller.h"
#include "chromeos/components/phonehub/message_receiver.h"
#include "chromeos/components/phonehub/mutable_phone_model.h"
#include "chromeos/components/phonehub/notification.h"
#include "chromeos/components/phonehub/notification_access_manager.h"
#include "chromeos/components/phonehub/notification_manager.h"
#include "chromeos/services/multidevice_setup/public/mojom/multidevice_setup.mojom.h"
#include "ui/gfx/image/image.h"
#include <algorithm>
#include <string>
namespace chromeos {
namespace phonehub {
namespace {
using multidevice_setup::MultiDeviceSetupClient;
gfx::Image CreateImageFromSerializedIcon(const std::string& bytes) {
return gfx::Image::CreateFrom1xPNGBytes(
reinterpret_cast<const unsigned char*>(bytes.c_str()), bytes.size());
}
Notification::Importance GetNotificationImportanceFromProto(
proto::NotificationImportance importance) {
switch (importance) {
case proto::NotificationImportance::UNSPECIFIED:
return Notification::Importance::kUnspecified;
case proto::NotificationImportance::NONE:
return Notification::Importance::kNone;
case proto::NotificationImportance::MIN:
return Notification::Importance::kMin;
case proto::NotificationImportance::LOW:
return Notification::Importance::kLow;
case proto::NotificationImportance::DEFAULT:
return Notification::Importance::kDefault;
case proto::NotificationImportance::HIGH:
return Notification::Importance::kHigh;
default:
return Notification::Importance::kUnspecified;
}
}
PhoneStatusModel::MobileStatus GetMobileStatusFromProto(
proto::MobileConnectionState mobile_status) {
switch (mobile_status) {
case proto::MobileConnectionState::NO_SIM:
return PhoneStatusModel::MobileStatus::kNoSim;
case proto::MobileConnectionState::SIM_BUT_NO_RECEPTION:
return PhoneStatusModel::MobileStatus::kSimButNoReception;
case proto::MobileConnectionState::SIM_WITH_RECEPTION:
return PhoneStatusModel::MobileStatus::kSimWithReception;
default:
return PhoneStatusModel::MobileStatus::kNoSim;
}
}
PhoneStatusModel::SignalStrength GetSignalStrengthFromProto(
proto::SignalStrength signal_strength) {
switch (signal_strength) {
case proto::SignalStrength::ZERO_BARS:
return PhoneStatusModel::SignalStrength::kZeroBars;
case proto::SignalStrength::ONE_BAR:
return PhoneStatusModel::SignalStrength::kOneBar;
case proto::SignalStrength::TWO_BARS:
return PhoneStatusModel::SignalStrength::kTwoBars;
case proto::SignalStrength::THREE_BARS:
return PhoneStatusModel::SignalStrength::kThreeBars;
case proto::SignalStrength::FOUR_BARS:
return PhoneStatusModel::SignalStrength::kFourBars;
default:
return PhoneStatusModel::SignalStrength::kZeroBars;
}
}
PhoneStatusModel::ChargingState GetChargingStateFromProto(
proto::ChargingState charging_state) {
switch (charging_state) {
case proto::ChargingState::NOT_CHARGING:
return PhoneStatusModel::ChargingState::kNotCharging;
case proto::ChargingState::CHARGING_AC:
case proto::ChargingState::CHARGING_WIRELESS:
return PhoneStatusModel::ChargingState::kChargingAc;
case proto::ChargingState::CHARGING_USB:
return PhoneStatusModel::ChargingState::kChargingUsb;
default:
return PhoneStatusModel::ChargingState::kNotCharging;
}
}
PhoneStatusModel::BatterySaverState GetBatterySaverStateFromProto(
proto::BatteryMode battery_mode) {
switch (battery_mode) {
case proto::BatteryMode::BATTERY_SAVER_OFF:
return PhoneStatusModel::BatterySaverState::kOff;
case proto::BatteryMode::BATTERY_SAVER_ON:
return PhoneStatusModel::BatterySaverState::kOn;
default:
return PhoneStatusModel::BatterySaverState::kOff;
}
}
NotificationAccessManager::AccessStatus ComputeNotificationAccessState(
const proto::PhoneProperties& phone_properties) {
// If the user has a Work Profile active, notification access is not allowed
// by Android. See https://crbug.com/1155151.
if (phone_properties.profile_type() == proto::ProfileType::WORK_PROFILE)
return NotificationAccessManager::AccessStatus::kProhibited;
if (phone_properties.notification_access_state() ==
proto::NotificationAccessState::ACCESS_GRANTED) {
return NotificationAccessManager::AccessStatus::kAccessGranted;
}
return NotificationAccessManager::AccessStatus::kAvailableButNotGranted;
}
FindMyDeviceController::Status ComputeFindMyDeviceStatus(
const proto::PhoneProperties& phone_properties) {
if (phone_properties.find_my_device_capability() ==
proto::FindMyDeviceCapability::NOT_ALLOWED) {
return FindMyDeviceController::Status::kRingingNotAvailable;
}
bool is_ringing =
phone_properties.ring_status() == proto::FindMyDeviceRingStatus::RINGING;
return is_ringing ? FindMyDeviceController::Status::kRingingOn
: FindMyDeviceController::Status::kRingingOff;
}
base::Optional<Notification> ProcessNotificationProto(
const proto::Notification& proto) {
// Only process notifications that are messaging apps with inline-replies.
auto actions_it = std::find_if(
proto.actions().begin(), proto.actions().end(), [](const auto& action) {
return action.type() == proto::Action_InputType::Action_InputType_TEXT;
});
if (actions_it == proto.actions().end())
return base::nullopt;
base::Optional<base::string16> title = base::nullopt;
if (!proto.title().empty())
title = base::UTF8ToUTF16(proto.title());
base::Optional<base::string16> text_content = base::nullopt;
if (!proto.text_content().empty())
text_content = base::UTF8ToUTF16(proto.text_content());
base::Optional<gfx::Image> shared_image = base::nullopt;
if (!proto.shared_image().empty())
shared_image = CreateImageFromSerializedIcon(proto.shared_image());
base::Optional<gfx::Image> contact_image = base::nullopt;
if (!proto.contact_image().empty())
contact_image = CreateImageFromSerializedIcon(proto.contact_image());
return Notification(
proto.id(),
Notification::AppMetadata(
base::UTF8ToUTF16(proto.origin_app().visible_name()),
proto.origin_app().package_name(),
CreateImageFromSerializedIcon(proto.origin_app().icon())),
base::Time::FromJsTime(proto.epoch_time_millis()),
GetNotificationImportanceFromProto(proto.importance()), actions_it->id(),
title, text_content, shared_image, contact_image);
}
PhoneStatusModel CreatePhoneStatusModel(const proto::PhoneProperties& proto) {
return PhoneStatusModel(
GetMobileStatusFromProto(proto.connection_state()),
PhoneStatusModel::MobileConnectionMetadata{
GetSignalStrengthFromProto(proto.signal_strength()),
base::UTF8ToUTF16(proto.mobile_provider())},
GetChargingStateFromProto(proto.charging_state()),
GetBatterySaverStateFromProto(proto.battery_mode()),
proto.battery_percentage());
}
} // namespace
PhoneStatusProcessor::PhoneStatusProcessor(
DoNotDisturbController* do_not_disturb_controller,
FeatureStatusProvider* feature_status_provider,
MessageReceiver* message_receiver,
FindMyDeviceController* find_my_device_controller,
NotificationAccessManager* notification_access_manager,
NotificationManager* notification_manager,
MultiDeviceSetupClient* multidevice_setup_client,
MutablePhoneModel* phone_model)
: do_not_disturb_controller_(do_not_disturb_controller),
feature_status_provider_(feature_status_provider),
message_receiver_(message_receiver),
find_my_device_controller_(find_my_device_controller),
notification_access_manager_(notification_access_manager),
notification_manager_(notification_manager),
multidevice_setup_client_(multidevice_setup_client),
phone_model_(phone_model) {
DCHECK(do_not_disturb_controller_);
DCHECK(feature_status_provider_);
DCHECK(message_receiver_);
DCHECK(find_my_device_controller_);
DCHECK(notification_access_manager_);
DCHECK(notification_manager_);
DCHECK(multidevice_setup_client_);
DCHECK(phone_model_);
message_receiver_->AddObserver(this);
feature_status_provider_->AddObserver(this);
multidevice_setup_client_->AddObserver(this);
MaybeSetPhoneModelName(multidevice_setup_client_->GetHostStatus().second);
}
PhoneStatusProcessor::~PhoneStatusProcessor() {
message_receiver_->RemoveObserver(this);
feature_status_provider_->RemoveObserver(this);
multidevice_setup_client_->RemoveObserver(this);
}
void PhoneStatusProcessor::ProcessReceivedNotifications(
const RepeatedPtrField<proto::Notification>& notification_protos) {
multidevice_setup::mojom::FeatureState feature_state =
multidevice_setup_client_->GetFeatureState(
multidevice_setup::mojom::Feature::kPhoneHubNotifications);
if (feature_state != multidevice_setup::mojom::FeatureState::kEnabledByUser) {
// Do not process any notifications if notifications are not enabled in
// settings.
return;
}
base::flat_set<Notification> notifications;
for (const auto& proto : notification_protos) {
base::Optional<Notification> notif = ProcessNotificationProto(proto);
if (notif.has_value())
notifications.emplace(*notif);
}
notification_manager_->SetNotificationsInternal(notifications);
}
void PhoneStatusProcessor::SetReceivedPhoneStatusModelStates(
const proto::PhoneProperties& phone_properties) {
phone_model_->SetPhoneStatusModel(CreatePhoneStatusModel(phone_properties));
do_not_disturb_controller_->SetDoNotDisturbStateInternal(
phone_properties.notification_mode() ==
proto::NotificationMode::DO_NOT_DISTURB_ON,
phone_properties.profile_type() != proto::ProfileType::WORK_PROFILE);
notification_access_manager_->SetAccessStatusInternal(
ComputeNotificationAccessState(phone_properties));
find_my_device_controller_->SetPhoneRingingStatusInternal(
ComputeFindMyDeviceStatus(phone_properties));
}
void PhoneStatusProcessor::MaybeSetPhoneModelName(
const base::Optional<multidevice::RemoteDeviceRef>& remote_device) {
if (!remote_device.has_value()) {
phone_model_->SetPhoneName(base::nullopt);
return;
}
phone_model_->SetPhoneName(base::UTF8ToUTF16(remote_device->name()));
}
void PhoneStatusProcessor::OnFeatureStatusChanged() {
// Reset phone model instance when but still keep the phone's name.
if (feature_status_provider_->GetStatus() !=
FeatureStatus::kEnabledAndConnected) {
phone_model_->SetPhoneStatusModel(base::nullopt);
notification_manager_->ClearNotificationsInternal();
}
}
void PhoneStatusProcessor::OnPhoneStatusSnapshotReceived(
proto::PhoneStatusSnapshot phone_status_snapshot) {
ProcessReceivedNotifications(phone_status_snapshot.notifications());
SetReceivedPhoneStatusModelStates(phone_status_snapshot.properties());
}
void PhoneStatusProcessor::OnPhoneStatusUpdateReceived(
proto::PhoneStatusUpdate phone_status_update) {
ProcessReceivedNotifications(phone_status_update.updated_notifications());
SetReceivedPhoneStatusModelStates(phone_status_update.properties());
if (!phone_status_update.removed_notification_ids().empty()) {
base::flat_set<int64_t> removed_notification_ids;
for (auto& id : phone_status_update.removed_notification_ids()) {
removed_notification_ids.emplace(id);
}
notification_manager_->RemoveNotificationsInternal(
removed_notification_ids);
}
}
void PhoneStatusProcessor::OnHostStatusChanged(
const MultiDeviceSetupClient::HostStatusWithDevice&
host_device_with_status) {
MaybeSetPhoneModelName(host_device_with_status.second);
}
} // namespace phonehub
} // namespace chromeos