Commit 706a5c51 authored by Richard Knoll's avatar Richard Knoll Committed by Commit Bot

[Nearby] Start discovery on discovery page

This calls the DiscoveryManager interface from JS and listens for
discovered and lost devices to update the device list. The selected
device is then passed through to the confirmation-page.

Bug: 1103190
Change-Id: I9296e5449533eb869fd9f20fe442d4ac1bc05a42
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2315862
Commit-Queue: Richard Knoll <knollr@chromium.org>
Reviewed-by: default avatarMichael van Ouwerkerk <mvanouwerkerk@chromium.org>
Cr-Commit-Position: refs/heads/master@{#791591}
parent 09c7e194
......@@ -59,6 +59,7 @@ js_library("nearby_discovery_page") {
"//chrome/browser/ui/webui/nearby_share:mojom_js_library_for_compile",
"//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
"//ui/webui/resources/cr_elements/cr_button:cr_button.m",
"//ui/webui/resources/js:assert.m",
]
}
......
<cr-view-manager id="viewManager">
<nearby-confirmation-page id="[[Page.CONFIRMATION]]" slot="view"
confirmation-manager="[[confirmationManager_]]"
confirmation-token="[[confirmationToken_]]">
confirmation-token="[[confirmationToken_]]"
share-target="[[selectedShareTarget_]]">
</nearby-confirmation-page>
<nearby-discovery-page id="[[Page.DISCOVERY]]" slot="view" class="active"
confirmation-manager="{{confirmationManager_}}"
confirmation-token="{{confirmationToken_}}">
confirmation-token="{{confirmationToken_}}"
selected-share-target="{{selectedShareTarget_}}">
</nearby-discovery-page>
<nearby-onboarding-page id="[[Page.ONBOARDING]]" slot="view">
</nearby-onboarding-page>
......
......@@ -56,6 +56,17 @@ Polymer({
type: String,
value: null,
},
/**
* The currently selected share target set by the nearby-discovery-page
* component when the user selects a device.
* @type {?nearbyShare.mojom.ShareTarget}
* @private
*/
selectedShareTarget_: {
type: Object,
value: null,
},
},
listeners: {'change-page': 'onChangePage_'},
......
......@@ -80,7 +80,7 @@
<div id="confirmation-token">
Secure connection ID: [[confirmationToken]]
</div>
<nearby-progress device-name="Pixel 2"></nearby-progress>
<nearby-progress device-name="[[shareTarget.name]]"></nearby-progress>
</div>
<cr-checkbox id="add-contacts">
......
......@@ -45,6 +45,16 @@ Polymer({
type: String,
value: null,
},
/**
* The selected share target to confirm the transfer for. Expected to start
* as null, then change to a valid object before this component is shown.
* @type {?nearbyShare.mojom.ShareTarget}
*/
shareTarget: {
type: Object,
value: null,
},
},
/** @private */
......
......@@ -95,6 +95,9 @@
<nearby-device name="Alyssa's Pixel"></nearby-device>
<nearby-device name="Shangela's Pixel 2XL"></nearby-device>
<nearby-device name="Mira's Chromebook"></nearby-device>
<template is="dom-repeat" items="[[shareTargets_]]">
<nearby-device name="[[item.name]]"></nearby-device>
</template>
</div>
</div>
......
......@@ -14,10 +14,29 @@ import './nearby_device.js';
import './nearby_preview.js';
import './nearby_share.mojom-lite.js';
import {assert} from 'chrome://resources/js/assert.m.js';
import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {getDiscoveryManager} from './discovery_manager.js';
/**
* Converts an unguessable token to a string.
* @param {!mojoBase.mojom.UnguessableToken} token
* @return {!string}
*/
function tokenToString(token) {
return `${token.high.toString()}#${token.low.toString()}`;
}
/**
* Compares two unguessable tokens.
* @param {!mojoBase.mojom.UnguessableToken} a
* @param {!mojoBase.mojom.UnguessableToken} b
*/
function tokensEqual(a, b) {
return a.high === b.high && a.low === b.low;
}
Polymer({
is: 'nearby-discovery-page',
......@@ -43,27 +62,122 @@ Polymer({
type: String,
value: null,
},
/**
* The currently selected share target.
* @type {?nearbyShare.mojom.ShareTarget}
*/
selectedShareTarget: {
notify: true,
type: Object,
value: null,
},
/**
* A list of all discovered nearby share targets.
* @private {!Array<!nearbyShare.mojom.ShareTarget>}
*/
shareTargets_: {
type: Array,
value: [],
},
},
/** @private {nearbyShare.mojom.ShareTargetListenerCallbackRouter} */
mojoEventTarget_: null,
/** @private {Array<number>} */
listenerIds_: null,
/**
* @type {Map<!string,!nearbyShare.mojom.ShareTarget>}
*/
shareTargetMap_: null,
/** @override */
attached() {
this.shareTargets_ = [];
this.shareTargetMap_ = new Map();
this.mojoEventTarget_ =
new nearbyShare.mojom.ShareTargetListenerCallbackRouter();
this.listenerIds_ = [
this.mojoEventTarget_.onShareTargetDiscovered.addListener(
this.onShareTargetDiscovered_.bind(this)),
this.mojoEventTarget_.onShareTargetLost.addListener(
this.onShareTargetLost_.bind(this)),
];
// TODO(knollr): Only do this when the discovery page is actually shown.
getDiscoveryManager()
.startDiscovery(this.mojoEventTarget_.$.bindNewPipeAndPassRemote())
.then(response => {
if (!response.success) {
// TODO(knollr): Show error.
return;
}
});
},
/** @override */
detached() {
this.listenerIds_.forEach(
id => assert(this.mojoEventTarget_.removeListener(id)));
this.mojoEventTarget_.$.close();
},
/**
* @private
* @param {!nearbyShare.mojom.ShareTarget} shareTarget The discovered device.
*/
onShareTargetDiscovered_(shareTarget) {
const shareTargetId = tokenToString(shareTarget.id);
if (!this.shareTargetMap_.has(shareTargetId)) {
this.push('shareTargets_', shareTarget);
} else {
const index = this.shareTargets_.findIndex(
(target) => tokensEqual(target.id, shareTarget.id));
assert(index !== -1);
this.splice('shareTargets_', index, 1, shareTarget);
}
this.shareTargetMap_.set(shareTargetId, shareTarget);
},
/**
* @private
* @param {!nearbyShare.mojom.ShareTarget} shareTarget The lost device.
*/
onShareTargetLost_(shareTarget) {
const index = this.shareTargets_.findIndex(
(target) => tokensEqual(target.id, shareTarget.id));
assert(index !== -1);
this.splice('shareTargets_', index, 1);
this.shareTargetMap_.delete(tokenToString(shareTarget.id));
},
/** @private */
onNextTap_() {
// TODO(knollr): Use the selected device after discovering it.
const shareTargetId = {high: 0, low: 17};
getDiscoveryManager().selectShareTarget(shareTargetId).then(response => {
const {result, token, confirmationManager} = response;
if (result !== nearbyShare.mojom.SelectShareTargetResult.kOk) {
// TODO(knollr): Show error.
return;
}
if (confirmationManager) {
this.confirmationManager = confirmationManager;
this.confirmationToken = token;
this.fire('change-page', {page: 'confirmation'});
} else {
// TODO(knollr): Close dialog as send is now in progress.
}
});
// TODO(knollr): Allow user to select share target and remove this.
this.selectedShareTarget = this.shareTargets_[0];
assert(this.selectedShareTarget);
getDiscoveryManager()
.selectShareTarget(this.selectedShareTarget.id)
.then(response => {
const {result, token, confirmationManager} = response;
if (result !== nearbyShare.mojom.SelectShareTargetResult.kOk) {
// TODO(knollr): Show error.
return;
}
if (confirmationManager) {
this.confirmationManager = confirmationManager;
this.confirmationToken = token;
this.fire('change-page', {page: 'confirmation'});
} else {
// TODO(knollr): Close dialog as send is now in progress.
}
});
},
});
......@@ -48,11 +48,12 @@ export class FakeDiscoveryManagerRemote extends TestBrowserProxy {
}
/**
* @param {!mojoBase.mojom.UnguessableToken} shareTargetId
* @suppress {checkTypes} FakeConfirmationManagerRemote does not extend
* ConfirmationManagerRemote but implements ConfirmationManagerInterface.
*/
async selectShareTarget() {
this.methodCalled('selectShareTarget');
async selectShareTarget(shareTargetId) {
this.methodCalled('selectShareTarget', shareTargetId);
return this.selectShareTargetResult;
}
......
......@@ -49,4 +49,17 @@ suite('ConfirmatonPageTest', function() {
confirmationPageElement.$$('#confirmation-token').textContent;
assertTrue(renderedToken.includes(token));
});
test('renders share target name', function() {
const name = 'Device Name';
confirmationPageElement.shareTarget = {
id: {high: 0, low: 0},
name,
type: nearbyShare.mojom.ShareTargetType.kPhone,
};
const renderedName = confirmationPageElement.$$('nearby-progress')
.$$('#device-name')
.textContent;
assertEquals(name, renderedName);
});
});
......@@ -8,7 +8,7 @@ import 'chrome://nearby/nearby_discovery_page.js';
import {setDiscoveryManagerForTesting} from 'chrome://nearby/discovery_manager.js';
import {assertEquals} from '../chai_assert.js';
import {assertEquals, assertFalse, assertTrue} from '../chai_assert.js';
import {FakeConfirmationManagerRemote, FakeDiscoveryManagerRemote} from './fake_mojo_interfaces.js';
......@@ -16,7 +16,55 @@ suite('DiscoveryPageTest', function() {
/** @type {!NearbyDiscoveryPageElement} */
let discoveryPageElement;
/** @type {!FakeDiscoveryManagerRemote} */
let discoveryManager;
/** @type {!number} Next device id to be used. */
let nextId = 0;
/**
* Get the list of device names that are currently shown.
* @return {!Array<string>}
*/
function getDeviceNames() {
/** @type {!Array<string>} */
const deviceNames = [];
for (const device of discoveryPageElement.$$('#device-list').children) {
if (device.is === 'nearby-device') {
deviceNames.push(device.$$('#name').textContent);
}
}
return deviceNames;
}
/**
* @param {!string} name Device name
* @return {!nearbyShare.mojom.ShareTarget}
*/
function createShareTarget(name) {
return {
id: {high: 0, low: nextId++},
name,
type: nearbyShare.mojom.ShareTargetType.kPhone,
};
}
/**
* Creates a share target and sends it to the WebUI.
* @return {!Promise<nearbyShare.mojom.ShareTarget>}
*/
async function setupShareTarget() {
/** @type {!nearbyShare.mojom.ShareTargetListenerRemote} */
const listener = await discoveryManager.whenCalled('startDiscovery');
const shareTarget = createShareTarget('Device Name');
listener.onShareTargetDiscovered(shareTarget);
await listener.$.flushForTesting();
return shareTarget;
}
setup(function() {
discoveryManager = new FakeDiscoveryManagerRemote();
setDiscoveryManagerForTesting(discoveryManager);
discoveryPageElement = /** @type {!NearbyDiscoveryPageElement} */ (
document.createElement('nearby-discovery-page'));
document.body.appendChild(discoveryPageElement);
......@@ -31,31 +79,27 @@ suite('DiscoveryPageTest', function() {
});
test('selects share target with success', async function() {
const manager = new FakeDiscoveryManagerRemote();
setDiscoveryManagerForTesting(manager);
const created = await setupShareTarget();
discoveryPageElement.$$('#next-button').click();
await manager.whenCalled('selectShareTarget');
const selectedId = await discoveryManager.whenCalled('selectShareTarget');
assertEquals(created.id.high, selectedId.high);
assertEquals(created.id.low, selectedId.low);
});
test('selects share target with error', async function() {
const manager = new FakeDiscoveryManagerRemote();
manager.selectShareTargetResult.result =
await setupShareTarget();
discoveryManager.selectShareTargetResult.result =
nearbyShare.mojom.SelectShareTargetResult.kError;
setDiscoveryManagerForTesting(manager);
discoveryPageElement.$$('#next-button').click();
await manager.whenCalled('selectShareTarget');
await discoveryManager.whenCalled('selectShareTarget');
});
test('selects share target with confirmation', async function() {
const manager = new FakeDiscoveryManagerRemote();
manager.selectShareTargetResult.token = 'test token';
manager.selectShareTargetResult.confirmationManager =
await setupShareTarget();
discoveryManager.selectShareTargetResult.token = 'test token';
discoveryManager.selectShareTargetResult.confirmationManager =
new FakeConfirmationManagerRemote();
setDiscoveryManagerForTesting(manager);
let eventDetail = null;
discoveryPageElement.addEventListener('change-page', (event) => {
......@@ -64,7 +108,66 @@ suite('DiscoveryPageTest', function() {
discoveryPageElement.$$('#next-button').click();
await manager.whenCalled('selectShareTarget');
await discoveryManager.whenCalled('selectShareTarget');
assertEquals('confirmation', eventDetail.page);
});
test('starts discovery', async function() {
await discoveryManager.whenCalled('startDiscovery');
});
test('shows newly discovered device', async function() {
/** @type {!nearbyShare.mojom.ShareTargetListenerRemote} */
const listener = await discoveryManager.whenCalled('startDiscovery');
const deviceName = 'Device Name';
listener.onShareTargetDiscovered(createShareTarget(deviceName));
await listener.$.flushForTesting();
assertTrue(getDeviceNames().includes(deviceName));
});
test('shows multiple discovered devices', async function() {
/** @type {!nearbyShare.mojom.ShareTargetListenerRemote} */
const listener = await discoveryManager.whenCalled('startDiscovery');
const deviceName1 = 'Device Name 1';
const deviceName2 = 'Device Name 2';
listener.onShareTargetDiscovered(createShareTarget(deviceName1));
listener.onShareTargetDiscovered(createShareTarget(deviceName2));
await listener.$.flushForTesting();
assertTrue(getDeviceNames().includes(deviceName1));
assertTrue(getDeviceNames().includes(deviceName2));
});
test('removes lost device', async function() {
/** @type {!nearbyShare.mojom.ShareTargetListenerRemote} */
const listener = await discoveryManager.whenCalled('startDiscovery');
const deviceName = 'Device Name';
const shareTarget = createShareTarget(deviceName);
listener.onShareTargetDiscovered(shareTarget);
listener.onShareTargetLost(shareTarget);
await listener.$.flushForTesting();
assertFalse(getDeviceNames().includes(deviceName));
});
test('replaces existing device', async function() {
/** @type {!nearbyShare.mojom.ShareTargetListenerRemote} */
const listener = await discoveryManager.whenCalled('startDiscovery');
const deviceName = 'Device Name';
const shareTarget = createShareTarget(deviceName);
listener.onShareTargetDiscovered(shareTarget);
await listener.$.flushForTesting();
const expectedDeviceCount = getDeviceNames().length;
listener.onShareTargetDiscovered(shareTarget);
await listener.$.flushForTesting();
assertEquals(expectedDeviceCount, getDeviceNames().length);
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment