Skip to content

Commit

Permalink
[Cast2Class] Add managed device footnote
Browse files Browse the repository at this point in the history
Conditionally show a footnote in the access code cast dialog which
informs the user that their cast device will be saved.

Bug: b:218574288
Change-Id: Ia2ae210d3aee4c03463975c12427675250d1339d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3639845
Reviewed-by: Brian Malcolm <bmalcolm@chromium.org>
Commit-Queue: Benjamin Zielinski <bzielinski@google.com>
Cr-Commit-Position: refs/heads/main@{#1002294}
  • Loading branch information
Benjamin Zielinski authored and Chromium LUCI CQ committed May 11, 2022
1 parent f0f5ebc commit 39b635c
Show file tree
Hide file tree
Showing 11 changed files with 184 additions and 5 deletions.
20 changes: 20 additions & 0 deletions chrome/app/access_code_cast_strings.grdp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,26 @@
<message name="IDS_ACCESS_CODE_CAST_INPUT_ARIA_LABEL" is_accessibility_with_no_ui="true" desc="Label for the text box where users type the access code to start casting to a Chromecast device">
Type the access code to start casting
</message>
<message name="IDS_ACCESS_CODE_CAST_MANAGED_FOOTNOTE_DAYS" desc="In the dialog to connect to a chromecast device using a code, this message is a footnote that lets the user know that the Chromecast device will be remembered and will remain in their list for a number of days">
{DAYS, plural,
=1 {This device will be saved for 1 day and you can connect without a code next time. This is set by your administrator.}
other {This device will be saved for {DAYS} days and you can connect without a code next time. This is set by your administrator.}}
</message>
<message name="IDS_ACCESS_CODE_CAST_MANAGED_FOOTNOTE_HOURS" desc="In the dialog to connect to a chromecast device using a code, this message is a footnote that lets the user know that the Chromecast device will be remembered and will remain in their list for a number of hours">
{HOURS, plural,
=1 {This device will be saved for 1 hour and you can connect without a code next time. This is set by your administrator.}
other {This device will be saved for {HOURS} hours and you can connect without a code next time. This is set by your administrator.}}
</message>
<message name="IDS_ACCESS_CODE_CAST_MANAGED_FOOTNOTE_MONTHS" desc="In the dialog to connect to a chromecast device using a code, this message is a footnote that lets the user know that the Chromecast device will be remembered and will remain in their list for a number of months">
{MONTHS, plural,
=1 {This device will be saved for 1 month and you can connect without a code next time. This is set by your administrator.}
other {This device will be saved for {MONTHS} months and you can connect without a code next time. This is set by your administrator.}}
</message>
<message name="IDS_ACCESS_CODE_CAST_MANAGED_FOOTNOTE_YEARS" desc="In the dialog to connect to a chromecast device using a code, this message is a footnote that lets the user know that the Chromecast device will be remembered and will remain in their list for a number of years">
{YEARS, plural,
=1 {This device will be saved for 1 year and you can connect without a code next time. This is set by your administrator.}
other {This device will be saved for {YEARS} years and you can connect without a code next time. This is set by your administrator.}}
</message>
<message name="IDS_ACCESS_CODE_CAST_SUBMIT" desc="Text for the button that when pressed submits an access code to attempt to start casting">
Submit
</message>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
9b0992d53f87302cc98b9401ed8a386dc946a71f
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
5534dc4925d3946a668f9e660534a4f231541ad0
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
a0c29ea8ecd35913a0a28987c51f478238f72066
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
87832e23b136c4d6d278c457c515114fe5f56fa7
32 changes: 31 additions & 1 deletion chrome/browser/resources/access_code_cast/access_code_cast.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,25 @@
background-color: transparent;
}

#error-message-container {
min-height: 16px;
}

#remembered-device-footnote {
align-items: center;
display: flex;
font-size: 12px;
justify-content: center;
}

#remembered-device-icon {
align-self: flex-start;
flex-shrink: 0;
height: 20px;
padding-inline-end: 16px;
width: 20px;
}

a[href] {
color: var(--cr-link-color);
text-decoration: none;
Expand Down Expand Up @@ -80,7 +99,18 @@
<div id="qrInputView">
<div>Camera input view</div>
</div>
<c2c-error-message id="errorMessage"></c2c-error-message>
<div id="error-message-container">
<c2c-error-message id="errorMessage"></c2c-error-message>
</div>
<div class="space-1"></div>
<template is="dom-if" if="[[rememberDevices]]">
<div id="remembered-device-footnote">
<iron-icon icon="cr:domain" id="remembered-device-icon"></iron-icon>
<div id="remembered-device-content">
[[managedFootnote]]
</div>
</div>
</template>
</div>
<div slot="button-container">
<cr-button on-click="close" class="cancel-button">$i18n{cancel}</cr-button>
Expand Down
68 changes: 67 additions & 1 deletion chrome/browser/resources/access_code_cast/access_code_cast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
import {CrButtonElement} from 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
import {I18nMixin} from 'chrome://resources/js/i18n_mixin.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
import {PluralStringProxyImpl} from 'chrome://resources/js/plural_string_proxy.js';
import {WebUIListenerMixin} from 'chrome://resources/js/web_ui_listener_mixin.js';
import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';


import {AddSinkResultCode, CastDiscoveryMethod, PageCallbackRouter} from './access_code_cast.mojom-webui.js';
import {BrowserProxy} from './browser_proxy.js';
import {PasscodeInputElement} from './passcode_input/passcode_input.js';
Expand All @@ -44,6 +45,12 @@ export interface AccessCodeCastElement {
const AccessCodeCastElementBase =
WebUIListenerMixin(I18nMixin(PolymerElement));

const ECMASCRIPT_EPOCH_START_YEAR = 1970;
const SECONDS_PER_DAY = 86400;
const SECONDS_PER_HOUR = 3600;
const SECONDS_PER_MONTH = 2592000;
const SECONDS_PER_YEAR = 31536000;

export class AccessCodeCastElement extends AccessCodeCastElementBase {
static get is() {
return 'access-code-cast-app';
Expand Down Expand Up @@ -72,19 +79,25 @@ export class AccessCodeCastElement extends AccessCodeCastElementBase {
private router: PageCallbackRouter;

private static readonly ACCESS_CODE_LENGTH = 6;

private accessCode: string;
private canCast: boolean;
private inputLabel: string;
private state: PageState;
private submitDisabled: boolean;
private qrScannerEnabled: boolean;
private rememberDevices: boolean;
private managedFootnote: string;

constructor() {
super();
this.listenerIds = [];
this.router = BrowserProxy.getInstance().callbackRouter;
this.inputLabel = this.i18n('inputLabel');

this.createManagedFootnote(
loadTimeData.getInteger('rememberedDeviceDuration'));

this.accessCode = '';
BrowserProxy.getInstance().isQrScanningAvailable().then((available) => {
this.qrScannerEnabled = available;
Expand Down Expand Up @@ -168,10 +181,57 @@ export class AccessCodeCastElement extends AccessCodeCastElementBase {
this.close();
}

async createManagedFootnote(duration: number) {
if (duration === 0) {
return;
}


// Handle the cases from the policy enum.
if (duration === SECONDS_PER_HOUR) {
return this.makeFootnote('managedFootnoteHours', 1);
} else if (duration === SECONDS_PER_DAY) {
return this.makeFootnote('managedFootnoteDays', 1);
} else if (duration === SECONDS_PER_MONTH) {
return this.makeFootnote('managedFootnoteMonths', 1);
} else if (duration === SECONDS_PER_YEAR) {
return this.makeFootnote('managedFootnoteYears', 1);
}

// Handle the general case.
const durationAsDate = new Date(duration * 1000);
// ECMAscript epoch starts at 1970.
if (durationAsDate.getUTCFullYear() - ECMASCRIPT_EPOCH_START_YEAR > 0) {
return this.makeFootnote('managedFootnoteYears',
durationAsDate.getUTCFullYear() - ECMASCRIPT_EPOCH_START_YEAR);
// Months are zero indexed.
} else if (durationAsDate.getUTCMonth() > 0) {
return this.makeFootnote('managedFootnoteMonths',
durationAsDate.getUTCMonth());
// Dates start at 1.
} else if (durationAsDate.getUTCDate() - 1 > 0) {
return this.makeFootnote('managedFootnoteDays',
durationAsDate.getUTCDate() - 1);
// Hours start at 0.
} else if (durationAsDate.getUTCHours() > 0) {
return this.makeFootnote('managedFootnoteHours',
durationAsDate.getUTCHours());
// The given duration is either minutes, seconds, or a negative time. These
// are not valid so we should not show the managed footnote.
}

this.rememberDevices = false;
return;
}

setAccessCodeForTest(value: string) {
this.accessCode = value;
}

getManagedFootnoteForTest() {
return this.managedFootnote;
}

private castStateChange() {
this.submitDisabled = !this.canCast ||
this.accessCode.length !== AccessCodeCastElement.ACCESS_CODE_LENGTH;
Expand Down Expand Up @@ -231,6 +291,12 @@ export class AccessCodeCastElement extends AccessCodeCastElementBase {
const castResult = await BrowserProxy.getInstance().handler.castToSink();
return castResult.resultCode as RouteRequestResultCode;
}

private async makeFootnote(messageName: string, value: number) {
const proxy = PluralStringProxyImpl.getInstance();
this.managedFootnote = await proxy.getPluralString(messageName, value);
this.rememberDevices = true;
}
}

declare global {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,12 @@ void AccessCodeCastDialog::GetWebUIMessageHandlers(
void AccessCodeCastDialog::GetDialogSize(gfx::Size* size) const {
const int kDefaultWidth = 448;
const int kDefaultHeight = 271;
size->SetSize(kDefaultWidth, kDefaultHeight);
const int kRememberDevicesHeight = 310;
base::TimeDelta duration_pref = GetAccessCodeDeviceDurationPref(
context_->GetPrefs());
bool rememberDevices = duration_pref != base::Seconds(0);
size->SetSize(kDefaultWidth,
rememberDevices ? kRememberDevicesHeight : kDefaultHeight);
}

std::string AccessCodeCastDialog::GetDialogArgs() const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include "base/memory/raw_ptr.h"
#include "base/time/time.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/media_router/media_cast_mode.h"
#include "chrome/browser/ui/media_router/media_route_starter.h"
#include "components/access_code_cast/common/access_code_cast_metrics.h"
Expand All @@ -17,7 +18,6 @@
#include "url/gurl.h"

namespace content {
class BrowserContext;
class WebContents;
} // namespace content

Expand Down Expand Up @@ -93,7 +93,7 @@ class AccessCodeCastDialog : public ui::WebDialogDelegate,
std::unique_ptr<media_router::MediaRouteStarter> media_route_starter_;

const raw_ptr<content::WebContents> web_contents_;
const raw_ptr<content::BrowserContext> context_;
const raw_ptr<Profile> context_;
base::Time dialog_creation_timestamp_;
};

Expand Down
20 changes: 20 additions & 0 deletions chrome/browser/ui/webui/access_code_cast/access_code_cast_ui.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@

#include "chrome/browser/ui/webui/access_code_cast/access_code_cast_ui.h"

#include "chrome/browser/media/router/discovery/access_code/access_code_cast_feature.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/webui/access_code_cast/access_code_cast_dialog.h"
#include "chrome/browser/ui/webui/plural_string_handler.h"
#include "chrome/browser/ui/webui/webui_util.h"
#include "chrome/common/url_constants.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/grit/access_code_cast_resources.h"
#include "chrome/grit/access_code_cast_resources_map.h"
#include "chrome/grit/generated_resources.h"
#include "components/media_router/browser/media_router_factory.h"
#include "components/prefs/pref_service.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/web_ui_data_source.h"
Expand Down Expand Up @@ -51,6 +55,22 @@ AccessCodeCastUI::AccessCodeCastUI(content::WebUI* web_ui)
source->AddBoolean("qrScannerEnabled", false);
source->AddString("learnMoreUrl", chrome::kAccessCodeCastLearnMoreURL);

Profile* const profile = Profile::FromWebUI(web_ui);
source->AddInteger("rememberedDeviceDuration",
GetAccessCodeDeviceDurationPref(profile->GetPrefs()).InSeconds());

// Add a handler to provide pluralized strings.
auto plural_string_handler = std::make_unique<PluralStringHandler>();
plural_string_handler->AddLocalizedString(
"managedFootnoteHours", IDS_ACCESS_CODE_CAST_MANAGED_FOOTNOTE_HOURS);
plural_string_handler->AddLocalizedString(
"managedFootnoteDays", IDS_ACCESS_CODE_CAST_MANAGED_FOOTNOTE_DAYS);
plural_string_handler->AddLocalizedString(
"managedFootnoteMonths", IDS_ACCESS_CODE_CAST_MANAGED_FOOTNOTE_MONTHS);
plural_string_handler->AddLocalizedString(
"managedFootnoteYears", IDS_ACCESS_CODE_CAST_MANAGED_FOOTNOTE_YEARS);
web_ui->AddMessageHandler(std::move(plural_string_handler));

content::BrowserContext* browser_context =
web_ui->GetWebContents()->GetBrowserContext();
content::WebUIDataSource::Add(browser_context, source.release());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,4 +260,38 @@ suite('AccessCodeCastAppTest', () => {
await app.addSinkAndCast();
assertTrue(app.$.codeInput.focused);
});

// Split up footnote tests to limit number of await statements used in a
// single test, since this contributes to test flakiness.
test('managed footnote is correctly created for short times', async () => {
assertEquals(app.getManagedFootnoteForTest(), undefined);

await app.createManagedFootnote(3600 /* One hour */);
assertTrue(app.getManagedFootnoteForTest().includes('1 hour '));

await app.createManagedFootnote(7200 /* Two hours */);
assertTrue(app.getManagedFootnoteForTest().includes('2 hours '));

await app.createManagedFootnote(86400 /* 1 day */);
assertTrue(app.getManagedFootnoteForTest().includes('1 day '));

await app.createManagedFootnote(172800 /* 2 days */);
assertTrue(app.getManagedFootnoteForTest().includes('2 days '));
});

test('managed footnote is correctly created for long times', async () => {
assertEquals(app.getManagedFootnoteForTest(), undefined);

await app.createManagedFootnote(2764800 /* 32 days */);
assertTrue(app.getManagedFootnoteForTest().includes('1 month '));

await app.createManagedFootnote(5529600 /* 64 days */);
assertTrue(app.getManagedFootnoteForTest().includes('2 months '));

await app.createManagedFootnote(31540000 /* 1 year */);
assertTrue(app.getManagedFootnoteForTest().includes('1 year '));

await app.createManagedFootnote(63080000 /* 2 years */);
assertTrue(app.getManagedFootnoteForTest().includes('2 years '));
});
});

0 comments on commit 39b635c

Please sign in to comment.