|  | // 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 {globals} from '../frontend/globals'; | 
|  |  | 
|  | 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 constructor(adb: Adb, consumer: Consumer) { | 
|  | super(consumer); | 
|  | this.adb = adb; | 
|  | } | 
|  |  | 
|  | 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(); | 
|  | if (!this.device) { | 
|  | this.state = AdbConnectionState.READY_TO_CONNECT; | 
|  | 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 = 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${ | 
|  | 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<USBDevice|undefined> { | 
|  | 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); | 
|  | } | 
|  | } |