blob: 2a72a33e85e38201f80d1692633da2ffb5813f99 [file] [log] [blame]
// Copyright (C) 2019 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {exists} from '../base/utils';
import {RecordingState, RecordingTarget, isAdbTarget} from '../common/state';
import {
extractDurationFromTraceConfig,
extractTraceConfig,
} from '../core/trace_config_utils';
import {Adb} from './adb_interfaces';
import {ReadBuffersResponse} from './consumer_port_types';
import {Consumer, RpcConsumerPort} from './record_controller_interfaces';
export enum AdbConnectionState {
READY_TO_CONNECT,
AUTH_IN_PROGRESS,
CONNECTED,
CLOSED,
}
interface Command {
method: string;
params: Uint8Array;
}
export abstract class AdbBaseConsumerPort extends RpcConsumerPort {
// Contains the commands sent while the authentication is in progress. They
// will all be executed afterwards. If the device disconnects, they are
// removed.
private commandQueue: Command[] = [];
protected adb: Adb;
protected state = AdbConnectionState.READY_TO_CONNECT;
protected device?: USBDevice;
protected recState: RecordingState;
protected constructor(
adb: Adb,
consumer: Consumer,
recState: RecordingState,
) {
super(consumer);
this.adb = adb;
this.recState = recState;
}
async handleCommand(method: string, params: Uint8Array) {
try {
if (method === 'FreeBuffers') {
// When we finish tracing, we disconnect the adb device interface.
// Otherwise, we will keep holding the device interface and won't allow
// adb to access it. https://wicg.github.io/webusb/#abusing-a-device
// "Lastly, since USB devices are unable to distinguish requests from
// multiple sources, operating systems only allow a USB interface to
// have a single owning user-space or kernel-space driver."
this.state = AdbConnectionState.CLOSED;
await this.adb.disconnect();
} else if (method === 'EnableTracing') {
this.state = AdbConnectionState.READY_TO_CONNECT;
}
if (this.state === AdbConnectionState.CLOSED) return;
this.commandQueue.push({method, params});
if (
this.state === AdbConnectionState.READY_TO_CONNECT ||
this.deviceDisconnected()
) {
this.state = AdbConnectionState.AUTH_IN_PROGRESS;
this.device = await this.findDevice(this.recState.recordingTarget);
if (!this.device) {
this.state = AdbConnectionState.READY_TO_CONNECT;
const target = this.recState.recordingTarget;
throw Error(
`Device with serial ${
isAdbTarget(target) ? target.serial : 'n/a'
} not found.`,
);
}
this.sendStatus(`Please allow USB debugging on device.
If you press cancel, reload the page.`);
await this.adb.connect(this.device);
// During the authentication the device may have been disconnected.
if (!this.recState.recordingInProgress || this.deviceDisconnected()) {
throw Error('Recording not in progress after adb authorization.');
}
this.state = AdbConnectionState.CONNECTED;
this.sendStatus('Device connected.');
}
if (this.state === AdbConnectionState.AUTH_IN_PROGRESS) return;
console.assert(this.state === AdbConnectionState.CONNECTED);
for (const cmd of this.commandQueue) this.invoke(cmd.method, cmd.params);
this.commandQueue = [];
} catch (e) {
this.commandQueue = [];
this.state = AdbConnectionState.READY_TO_CONNECT;
this.sendErrorMessage(e.message);
}
}
private deviceDisconnected() {
return !this.device || !this.device.opened;
}
setDurationStatus(enableTracingProto: Uint8Array) {
const traceConfigProto = extractTraceConfig(enableTracingProto);
if (!traceConfigProto) return;
const duration = extractDurationFromTraceConfig(traceConfigProto);
this.sendStatus(
`Recording in progress${
exists(duration) ? ' for ' + duration.toString() + ' ms' : ''
}...`,
);
}
abstract invoke(method: string, argsProto: Uint8Array): void;
generateChunkReadResponse(
data: Uint8Array,
last = false,
): ReadBuffersResponse {
return {
type: 'ReadBuffersResponse',
slices: [{data, lastSliceForPacket: last}],
};
}
async findDevice(
connectedDevice: RecordingTarget,
): Promise<USBDevice | undefined> {
if (!('usb' in navigator)) return undefined;
if (!isAdbTarget(connectedDevice)) return undefined;
const devices = await navigator.usb.getDevices();
return devices.find((d) => d.serialNumber === connectedDevice.serial);
}
}