// 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 {extractDurationFromTraceConfig} from '../base/trace_config_utils'; import {extractTraceConfig} from '../base/trace_config_utils'; import {isAdbTarget} from '../common/state'; import {Adb} from './adb_interfaces'; import {ReadBuffersResponse} from './consumer_port_types'; import {globals} from './globals'; import {Consumer, RpcConsumerPort} from './record_controller_interfaces'; export enum AdbAuthState { DISCONNECTED, AUTH_IN_PROGRESS, CONNECTED, } 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 = AdbAuthState.DISCONNECTED; protected device?: USBDevice; constructor(adb: Adb, consumer: Consumer) { super(consumer); this.adb = adb; } async handleCommand(method: string, params: Uint8Array) { try { this.commandQueue.push({method, params}); if (this.state === AdbAuthState.DISCONNECTED || this.deviceDisconnected()) { this.state = AdbAuthState.AUTH_IN_PROGRESS; this.device = await this.findDevice(); if (!this.device) { this.state = AdbAuthState.DISCONNECTED; const target = globals.state.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 (!globals.state.recordingInProgress || this.deviceDisconnected()) { throw Error('Recording not in progress after adb authorization.'); } this.state = AdbAuthState.CONNECTED; this.sendStatus('Device connected.'); } if (this.state === AdbAuthState.AUTH_IN_PROGRESS) return; console.assert(this.state === AdbAuthState.CONNECTED); for (const cmd of this.commandQueue) this.invoke(cmd.method, cmd.params); this.commandQueue = []; } catch (e) { this.commandQueue = []; this.state = AdbAuthState.DISCONNECTED; 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${ 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(): Promise { if (!('usb' in navigator)) return undefined; const connectedDevice = globals.state.recordingTarget; if (!isAdbTarget(connectedDevice)) return undefined; const devices = await navigator.usb.getDevices(); return devices.find(d => d.serialNumber === connectedDevice.serial); } }