// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/printing/cups_print_job_notification_utils.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/ash/printing/cups_print_job.h"
#include "chrome/browser/chromeos/printing/printer_error_codes.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/branded_strings.h"
#include "chrome/grit/generated_resources.h"
#include "components/prefs/pref_service.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/message_center/public/cpp/notification.h"
namespace ash {
namespace {
using ::chromeos::PrinterErrorCode;
std::u16string GetAppShortNameUTF16(Profile* profile,
const std::string& app_id) {
if (apps::AppServiceProxyFactory::IsAppServiceAvailableForProfile(profile)) {
std::optional<std::u16string> app_name;
apps::AppServiceProxyFactory::GetForProfile(profile)
->AppRegistryCache()
.ForOneApp(app_id, [&app_name](const apps::AppUpdate& update) {
app_name = base::UTF8ToUTF16(update.ShortName());
});
if (app_name) {
return *app_name;
}
}
// If no app name could be inferred, go with `Chrome` as a fallback option.
return l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME);
}
std::u16string GetNotificationTitleForFailure(const CupsPrintJob& job) {
DCHECK_EQ(CupsPrintJob::State::STATE_FAILED, job.state());
switch (job.error_code()) {
case PrinterErrorCode::CLIENT_UNAUTHORIZED:
return l10n_util::GetStringUTF16(
IDS_PRINT_JOB_AUTHORIZATION_ERROR_NOTIFICATION_TITLE);
case PrinterErrorCode::EXPIRED_CERTIFICATE:
return l10n_util::GetStringUTF16(
IDS_PRINT_JOB_EXPIRED_CERT_ERROR_NOTIFICATION_TITLE);
default:
return l10n_util::GetStringUTF16(IDS_PRINT_JOB_ERROR_NOTIFICATION_TITLE);
}
}
std::u16string GetNotificationTitleForError(const CupsPrintJob& job) {
DCHECK_EQ(CupsPrintJob::State::STATE_ERROR, job.state());
switch (job.error_code()) {
case PrinterErrorCode::PAPER_JAM:
return l10n_util::GetStringUTF16(
IDS_PRINT_JOB_PAPER_JAM_NOTIFICATION_TITLE);
case PrinterErrorCode::OUT_OF_INK:
return l10n_util::GetStringUTF16(
IDS_PRINT_JOB_OUT_OF_INK_NOTIFICATION_TITLE);
case PrinterErrorCode::OUT_OF_PAPER:
return l10n_util::GetStringUTF16(
IDS_PRINT_JOB_OUT_OF_PAPER_NOTIFICATION_TITLE);
case PrinterErrorCode::DOOR_OPEN:
return l10n_util::GetStringUTF16(
IDS_PRINT_JOB_DOOR_OPEN_NOTIFICATION_TITLE);
case PrinterErrorCode::PRINTER_UNREACHABLE:
return l10n_util::GetStringUTF16(
IDS_PRINT_JOB_PRINTER_UNREACHABLE_NOTIFICATION_TITLE);
case PrinterErrorCode::TRAY_MISSING:
return l10n_util::GetStringUTF16(
IDS_PRINT_JOB_TRAY_MISSING_NOTIFICATION_TITLE);
case PrinterErrorCode::OUTPUT_FULL:
return l10n_util::GetStringUTF16(
IDS_PRINT_JOB_OUTPUT_FULL_NOTIFICATION_TITLE);
case PrinterErrorCode::STOPPED:
return l10n_util::GetStringUTF16(
IDS_PRINT_JOB_STOPPED_NOTIFICATION_TITLE);
case PrinterErrorCode::EXPIRED_CERTIFICATE:
return l10n_util::GetStringUTF16(
IDS_PRINT_JOB_EXPIRED_CERT_ERROR_NOTIFICATION_TITLE);
default:
return l10n_util::GetStringUTF16(IDS_PRINT_JOB_ERROR_NOTIFICATION_TITLE);
}
}
std::u16string GetNotificationBodyMessageForUnauthorizedClient(
const CupsPrintJob& job,
const Profile& profile) {
const std::u16string printer_name =
base::UTF8ToUTF16(job.printer().display_name());
bool send_username_and_filename_policy_enabled =
profile.GetPrefs()->GetBoolean(
prefs::kPrintingSendUsernameAndFilenameEnabled);
if (send_username_and_filename_policy_enabled) {
return l10n_util::GetStringFUTF16(
IDS_PRINT_JOB_NOTIFICATION_CLIENT_UNAUTHORIZED_MESSAGE,
base::UTF8ToUTF16(profile.GetProfileUserName()), printer_name);
} else {
return l10n_util::GetStringFUTF16(
IDS_PRINT_JOB_NOTIFICATION_IDENTIFICATION_REQUIRED_MESSAGE,
printer_name);
}
}
std::u16string GetNotificationBodyMessageForInProgressJob(
const CupsPrintJob& job,
Profile& profile) {
const std::u16string printer_name =
base::UTF8ToUTF16(job.printer().display_name());
auto app_name = GetAppShortNameUTF16(&profile, job.source_id());
if (job.total_page_number() > 1) {
const std::u16string pages =
base::NumberToString16(job.total_page_number());
return l10n_util::GetStringFUTF16(
IDS_PRINT_JOB_NOTIFICATION_APP_PRINTING_IN_PROGRESS, app_name, pages,
printer_name);
} else {
return l10n_util::GetStringFUTF16(
IDS_PRINT_JOB_NOTIFICATION_APP_PRINTING_IN_PROGRESS_SINGLE_PAGE,
app_name, printer_name);
}
}
std::u16string GetNotificationBodyMessageForCompletedJob(
const CupsPrintJob& job,
Profile& profile) {
const std::u16string printer_name =
base::UTF8ToUTF16(job.printer().display_name());
return l10n_util::GetStringFUTF16(
IDS_PRINT_JOB_NOTIFICATION_APP_PRINTING_DONE,
GetAppShortNameUTF16(&profile, job.source_id()), printer_name);
}
std::u16string GetNotificationBodyMessageForInterruptedJob(
const CupsPrintJob& job,
Profile& profile) {
const std::u16string printer_name =
base::UTF8ToUTF16(job.printer().display_name());
return l10n_util::GetStringFUTF16(
IDS_PRINT_JOB_NOTIFICATION_APP_PRINTING_INTERRUPTED,
GetAppShortNameUTF16(&profile, job.source_id()), printer_name);
}
} // namespace
namespace printing::internal {
void UpdateNotificationTitle(message_center::Notification* notification,
const CupsPrintJob& job) {
switch (job.state()) {
case CupsPrintJob::State::STATE_WAITING:
case CupsPrintJob::State::STATE_STARTED:
case CupsPrintJob::State::STATE_PAGE_DONE:
case CupsPrintJob::State::STATE_SUSPENDED:
case CupsPrintJob::State::STATE_RESUMED:
notification->set_title(
l10n_util::GetStringUTF16(IDS_PRINT_JOB_PRINTING_NOTIFICATION_TITLE));
break;
case CupsPrintJob::State::STATE_DOCUMENT_DONE:
notification->set_title(
l10n_util::GetStringUTF16(IDS_PRINT_JOB_DONE_NOTIFICATION_TITLE));
break;
case CupsPrintJob::State::STATE_FAILED:
notification->set_title(GetNotificationTitleForFailure(job));
break;
case CupsPrintJob::State::STATE_ERROR:
notification->set_title(GetNotificationTitleForError(job));
break;
case CupsPrintJob::State::STATE_CANCELLED:
NOTREACHED_IN_MIGRATION();
break;
default:
break;
}
}
void UpdateNotificationIcon(message_center::Notification* notification,
const CupsPrintJob& job) {
switch (job.state()) {
case CupsPrintJob::State::STATE_WAITING:
case CupsPrintJob::State::STATE_STARTED:
case CupsPrintJob::State::STATE_PAGE_DONE:
case CupsPrintJob::State::STATE_SUSPENDED:
case CupsPrintJob::State::STATE_RESUMED:
notification->set_accent_color_id(cros_tokens::kCrosSysPrimary);
notification->set_vector_small_image(kNotificationPrintingIcon);
break;
case CupsPrintJob::State::STATE_DOCUMENT_DONE:
notification->set_accent_color_id(cros_tokens::kCrosSysPrimary);
notification->set_vector_small_image(kNotificationPrintingDoneIcon);
break;
case CupsPrintJob::State::STATE_FAILED:
case CupsPrintJob::State::STATE_ERROR:
notification->set_accent_color_id(cros_tokens::kCrosSysError);
notification->set_vector_small_image(kNotificationPrintingWarningIcon);
break;
case CupsPrintJob::State::STATE_CANCELLED:
NOTREACHED_IN_MIGRATION();
break;
case CupsPrintJob::State::STATE_NONE:
break;
}
}
void UpdateNotificationBodyMessage(message_center::Notification* notification,
const CupsPrintJob& job,
Profile& profile) {
if (job.error_code() == PrinterErrorCode::CLIENT_UNAUTHORIZED) {
notification->set_message(
GetNotificationBodyMessageForUnauthorizedClient(job, profile));
return;
}
switch (job.state()) {
case CupsPrintJob::State::STATE_WAITING:
case CupsPrintJob::State::STATE_STARTED:
case CupsPrintJob::State::STATE_PAGE_DONE:
case CupsPrintJob::State::STATE_SUSPENDED:
case CupsPrintJob::State::STATE_RESUMED:
notification->set_message(
GetNotificationBodyMessageForInProgressJob(job, profile));
return;
case CupsPrintJob::State::STATE_DOCUMENT_DONE:
notification->set_message(
GetNotificationBodyMessageForCompletedJob(job, profile));
return;
case CupsPrintJob::State::STATE_FAILED:
case CupsPrintJob::State::STATE_ERROR:
notification->set_message(
GetNotificationBodyMessageForInterruptedJob(job, profile));
return;
case CupsPrintJob::State::STATE_CANCELLED:
NOTREACHED_IN_MIGRATION();
return;
case CupsPrintJob::State::STATE_NONE:
return;
}
}
} // namespace printing::internal
} // namespace ash