chromium/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_user_provider_impl.cc

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_user_provider_impl.h"

#include "ash/public/cpp/default_user_image.h"
#include "ash/public/cpp/personalization_app/user_display_info.h"
#include "ash/webui/personalization_app/mojom/personalization_app.mojom-forward.h"
#include "ash/webui/personalization_app/mojom/personalization_app.mojom.h"
#include "ash/webui/personalization_app/mojom/personalization_app_mojom_traits.h"
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/memory/scoped_refptr.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/ash/login/users/avatar/user_image_file_selector.h"
#include "chrome/browser/ash/login/users/avatar/user_image_manager_impl.h"
#include "chrome/browser/ash/login/users/avatar/user_image_manager_registry.h"
#include "chrome/browser/ash/login/users/avatar/user_image_prefs.h"
#include "chrome/browser/ash/login/users/default_user_image/default_user_images.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_manager.h"
#include "chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_manager_factory.h"
#include "chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chromeos/ash/components/camera_presence_notifier/camera_presence_notifier.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_image/user_image.h"
#include "components/user_manager/user_manager.h"
#include "content/public/browser/web_ui.h"
#include "mojo/public/cpp/base/big_buffer.h"
#include "mojo/public/cpp/bindings/message.h"
#include "services/data_decoder/public/cpp/decode_image.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/webui/web_ui_util.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/geometry/size.h"
#include "url/gurl.h"

namespace ash::personalization_app {

namespace {

using ash::personalization_app::GetAccountId;
using ash::personalization_app::GetUser;

// Called on |image_encoding_task_runner_| sequence.
std::vector<unsigned char> ImageSkiaToPngBytes(const gfx::ImageSkia& image) {
  if (image.isNull()) {
    return {};
  }

  // Encode the image as png.
  std::vector<unsigned char> output;
  if (gfx::PNGCodec::EncodeBGRASkBitmap(*image.bitmap(),
                                        /*discard_transparency=*/false,
                                        &output)) {
    return output;
  }

  // Return empty vector if case encoding failed.
  return {};
}

}  // namespace

PersonalizationAppUserProviderImpl::CameraImageDecoder::CameraImageDecoder() =
    default;

PersonalizationAppUserProviderImpl::CameraImageDecoder::~CameraImageDecoder() =
    default;

void PersonalizationAppUserProviderImpl::CameraImageDecoder::DecodeCameraImage(
    base::span<const uint8_t> encoded_bytes,
    data_decoder::DecodeImageCallback callback) {
  data_decoder::DecodeImage(
      &data_decoder_, encoded_bytes, data_decoder::mojom::ImageCodec::kPng,
      /*shrink_to_fit=*/true, data_decoder::kDefaultMaxSizeInBytes,
      /*desired_image_frame_size=*/gfx::Size(), std::move(callback));
}

PersonalizationAppUserProviderImpl::PersonalizationAppUserProviderImpl(
    content::WebUI* web_ui)
    : profile_(Profile::FromWebUI(web_ui)),
      camera_image_decoder_(
          std::make_unique<
              PersonalizationAppUserProviderImpl::CameraImageDecoder>()),
      image_encoding_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
          {base::TaskPriority::USER_VISIBLE,
           base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN})),
      user_image_file_selector_(
          std::make_unique<ash::UserImageFileSelector>(web_ui)) {
  camera_presence_notifier_ =
      std::make_unique<CameraPresenceNotifier>(base::BindRepeating(
          &PersonalizationAppUserProviderImpl::OnCameraPresenceCheckDone,
          weak_ptr_factory_.GetWeakPtr()));
}

PersonalizationAppUserProviderImpl::~PersonalizationAppUserProviderImpl() {
  if (page_viewed_) {
    ::ash::personalization_app::PersonalizationAppManagerFactory::
        GetForBrowserContext(profile_)
            ->MaybeStartHatsTimer(
                ::ash::personalization_app::HatsSurveyType::kAvatar);
  }
}

void PersonalizationAppUserProviderImpl::BindInterface(
    mojo::PendingReceiver<ash::personalization_app::mojom::UserProvider>
        receiver) {
  user_receiver_.reset();
  user_receiver_.Bind(std::move(receiver));
}

void PersonalizationAppUserProviderImpl::SetUserImageObserver(
    mojo::PendingRemote<ash::personalization_app::mojom::UserImageObserver>
        observer) {
  // May already be bound if user refreshes page.
  user_image_observer_remote_.reset();
  user_image_observer_remote_.Bind(std::move(observer));
  DCHECK(user_manager::UserManager::IsInitialized());
  auto* user_manager = user_manager::UserManager::Get();
  if (!user_manager_observer_.IsObserving()) {
    user_manager_observer_.Observe(user_manager);
  }

  const auto* user = GetUser(profile_);

  // Call observers manually the first time to initialize state.
  OnUserImageChanged(*user);

  ash::UserImageManagerImpl* user_image_manager =
      ash::UserImageManagerRegistry::Get()->GetManager(GetAccountId(profile_));
  const gfx::ImageSkia& profile_image =
      user_image_manager->DownloadedProfileImage();
  OnUserProfileImageUpdated(*user, profile_image);
  OnUserImageIsEnterpriseManagedChanged(
      *user, user_image_manager->IsUserImageManaged());

  camera_presence_notifier_->Start();
}

void PersonalizationAppUserProviderImpl::GetUserInfo(
    GetUserInfoCallback callback) {
  const user_manager::User* user = GetUser(profile_);
  DCHECK(user);
  std::move(callback).Run(ash::personalization_app::UserDisplayInfo(*user));
}

void PersonalizationAppUserProviderImpl::GetDefaultUserImages(
    GetDefaultUserImagesCallback callback) {
  page_viewed_ = true;
  std::vector<ash::default_user_image::DefaultUserImage> images =
      ash::default_user_image::GetCurrentImageSet();
  std::move(callback).Run(std::move(images));
}

void PersonalizationAppUserProviderImpl::SelectImageFromDisk() {
  if (!IsCustomizationSelectorsPrefEnabled()) {
    user_receiver_.ReportBadMessage("Not allowed to select image file");
    return;
  }
  user_image_file_selector_->SelectFile(
      base::BindOnce(&PersonalizationAppUserProviderImpl::OnFileSelected,
                     weak_ptr_factory_.GetWeakPtr()),
      base::DoNothing());
}

void PersonalizationAppUserProviderImpl::SelectDefaultImage(int index) {
  if (!ash::default_user_image::IsInCurrentImageSet(index)) {
    user_receiver_.ReportBadMessage("Invalid user image selected");
    return;
  }

  if (GetUser(profile_)->image_index() != index) {
    ash::UserImageManagerImpl::RecordUserImageChanged(
        ash::UserImageManagerImpl::ImageIndexToHistogramIndex(index));
  }

  auto* user_image_manager =
      ash::UserImageManagerRegistry::Get()->GetManager(GetAccountId(profile_));

  user_image_manager->SaveUserDefaultImageIndex(index);
}

void PersonalizationAppUserProviderImpl::SelectProfileImage() {
  if (!IsCustomizationSelectorsPrefEnabled()) {
    user_receiver_.ReportBadMessage("Not allowed to select profile image");
    return;
  }

  if (GetUser(profile_)->image_index() !=
      user_manager::UserImage::Type::kProfile) {
    ash::UserImageManagerImpl::RecordUserImageChanged(
        ash::default_user_image::kHistogramImageFromProfile);
  }

  ash::UserImageManagerImpl* user_image_manager =
      ash::UserImageManagerRegistry::Get()->GetManager(GetAccountId(profile_));

  user_image_manager->SaveUserImageFromProfileImage();
}

void PersonalizationAppUserProviderImpl::SelectCameraImage(
    ::mojo_base::BigBuffer data) {
  if (!IsCustomizationSelectorsPrefEnabled()) {
    user_receiver_.ReportBadMessage("Not allowed to select camera image");
    return;
  }
  // Make a copy of the data.
  auto ref_counted = base::MakeRefCounted<base::RefCountedBytes>(data);
  // Get a view of the same data copied above.
  auto as_span = base::make_span(ref_counted->front(), ref_counted->size());

  camera_image_decoder_->DecodeCameraImage(
      as_span,
      base::BindOnce(&PersonalizationAppUserProviderImpl::OnCameraImageDecoded,
                     image_decode_weak_ptr_factory_.GetWeakPtr(),
                     std::move(ref_counted)));
}

void PersonalizationAppUserProviderImpl::SelectLastExternalUserImage() {
  if (!IsCustomizationSelectorsPrefEnabled()) {
    user_receiver_.ReportBadMessage(
        "Not allowed to select last external image");
    return;
  }

  if (!last_external_user_image_) {
    LOG(WARNING) << "No last external user image present";
    return;
  }

  if (GetUser(profile_)->image_index() !=
      user_manager::UserImage::Type::kExternal) {
    ash::UserImageManagerImpl::RecordUserImageChanged(
        ash::default_user_image::kHistogramImageExternal);
  }

  ash::UserImageManagerImpl* user_image_manager =
      ash::UserImageManagerRegistry::Get()->GetManager(GetAccountId(profile_));

  user_image_manager->SaveUserImage(std::move(last_external_user_image_));
}

void PersonalizationAppUserProviderImpl::OnFileSelected(
    const base::FilePath& path) {
  // No way to tell if this is a different external image than last time, so
  // always record it.
  ash::UserImageManagerImpl::RecordUserImageChanged(
      ash::default_user_image::kHistogramImageExternal);

  ash::UserImageManagerImpl* user_image_manager =
      ash::UserImageManagerRegistry::Get()->GetManager(GetAccountId(profile_));

  user_image_manager->SaveUserImageFromFile(path);
}

void PersonalizationAppUserProviderImpl::OnUserImageChanged(
    const user_manager::User& user) {
  const user_manager::User* desired_user = GetUser(profile_);
  DCHECK(desired_user);

  if (user.GetAccountId() != desired_user->GetAccountId()) {
    return;
  }

  // Cancel requests to encode and send external user image data because it is
  // now out of date.
  image_encoding_weak_ptr_factory_.InvalidateWeakPtrs();

  int image_index = desired_user->image_index();
  switch (image_index) {
    case user_manager::UserImage::Type::kInvalid: {
      UpdateUserImageObserver(
          ash::personalization_app::mojom::UserImage::NewInvalidImage(
              ash::personalization_app::mojom::InvalidImage::New()));
      break;
    }
    case user_manager::UserImage::Type::kExternal: {
      if (desired_user->image_format() == user_manager::UserImage::FORMAT_PNG &&
          desired_user->has_image_bytes()) {
        last_external_user_image_ = std::make_unique<user_manager::UserImage>(
            desired_user->GetImage(), desired_user->image_bytes(),
            user_manager::UserImage::ImageFormat::FORMAT_PNG);

        // No need to re-encode a png because already have encoded bytes.
        auto image_bytes = desired_user->image_bytes();
        UpdateUserImageObserver(
            ash::personalization_app::mojom::UserImage::NewExternalImage(
                mojo_base::BigBuffer(base::make_span(image_bytes->front(),
                                                     image_bytes->size()))));
      } else {
        // Defer saving |last_external_user_image| until it has been encoded to
        // png bytes.
        DCHECK(desired_user->GetImage().IsThreadSafe())
            << "User image loader marks user images as thread safe";
        image_encoding_task_runner_->PostTaskAndReplyWithResult(
            FROM_HERE,
            base::BindOnce(&ImageSkiaToPngBytes, desired_user->GetImage()),
            base::BindOnce(
                &PersonalizationAppUserProviderImpl::OnExternalUserImageEncoded,
                image_encoding_weak_ptr_factory_.GetWeakPtr()));
      }
      break;
    }
    case user_manager::UserImage::Type::kProfile: {
      UpdateUserImageObserver(
          ash::personalization_app::mojom::UserImage::NewProfileImage(
              ash::personalization_app::mojom::ProfileImage::New()));
      break;
    }
    default: {
      if (!ash::default_user_image::IsValidIndex(image_index)) {
        LOG(ERROR) << "Invalid image index received";
        break;
      }
      UpdateUserImageObserver(
          ash::personalization_app::mojom::UserImage::NewDefaultImage(
              ash::default_user_image::GetDefaultUserImage(image_index)));
      break;
    }
  }
}

void PersonalizationAppUserProviderImpl::OnUserImageIsEnterpriseManagedChanged(
    const user_manager::User& user,
    bool is_enterprise_managed) {
  if (user.GetAccountId() != GetUser(profile_)->GetAccountId()) {
    return;
  }

  user_image_observer_remote_->OnIsEnterpriseManagedChanged(
      is_enterprise_managed);
}

void PersonalizationAppUserProviderImpl::OnUserProfileImageUpdated(
    const user_manager::User& user,
    const gfx::ImageSkia& profile_image) {
  const user_manager::User* desired_user = GetUser(profile_);
  DCHECK(desired_user);

  if (user.GetAccountId() != desired_user->GetAccountId()) {
    return;
  }

  user_image_observer_remote_->OnUserProfileImageUpdated(
      profile_image.isNull()
          ? GURL()
          : GURL(webui::GetBitmapDataUrl(*profile_image.bitmap())));
}

void PersonalizationAppUserProviderImpl::OnCameraPresenceCheckDone(
    bool is_camera_present) {
  user_image_observer_remote_->OnCameraPresenceCheckDone(is_camera_present);
}

void PersonalizationAppUserProviderImpl::OnCameraImageDecoded(
    scoped_refptr<base::RefCountedBytes> bytes,
    const SkBitmap& decoded_bitmap) {
  if (decoded_bitmap.isNull()) {
    LOG(WARNING) << "Camera image failed decoding";
    return;
  }

  // Every time we decode a camera image it is new, so always record a metric
  // here.
  ash::UserImageManagerImpl::RecordUserImageChanged(
      ash::default_user_image::kHistogramImageFromCamera);

  auto user_image = std::make_unique<user_manager::UserImage>(
      gfx::ImageSkia::CreateFrom1xBitmap(decoded_bitmap), std::move(bytes),
      user_manager::UserImage::ImageFormat::FORMAT_PNG);
  // Image was successfully decoded so it is valid png data.
  user_image->MarkAsSafe();

  auto* user_image_manager =
      ash::UserImageManagerRegistry::Get()->GetManager(GetAccountId(profile_));

  user_image_manager->SaveUserImage(std::move(user_image));
}

void PersonalizationAppUserProviderImpl::OnExternalUserImageEncoded(
    std::vector<unsigned char> encoded_png) {
  const user_manager::User* user = GetUser(profile_);

  // Since we did the work of encoding user image to png bytes, save it now.
  // Makes a copy of |encoded_png|.
  last_external_user_image_ = std::make_unique<user_manager::UserImage>(
      user->GetImage(),
      base::MakeRefCounted<base::RefCountedBytes>(encoded_png),
      user_manager::UserImage::ImageFormat::FORMAT_PNG);

  UpdateUserImageObserver(
      ash::personalization_app::mojom::UserImage::NewExternalImage(
          mojo_base::BigBuffer(std::move(encoded_png))));
}

bool PersonalizationAppUserProviderImpl::IsCustomizationSelectorsPrefEnabled() {
  return user_image::prefs::IsCustomizationSelectorsPrefEnabled(
      profile_->GetPrefs());
}

void PersonalizationAppUserProviderImpl::UpdateUserImageObserver(
    ash::personalization_app::mojom::UserImagePtr user_image) {
  user_image_observer_remote_->OnUserImageChanged(std::move(user_image));
}

void PersonalizationAppUserProviderImpl::SetUserImageFileSelectorForTesting(
    std::unique_ptr<ash::UserImageFileSelector> file_selector) {
  user_image_file_selector_ = std::move(file_selector);
}
}  // namespace ash::personalization_app