1// Copyright (C) 2019 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import {extractDurationFromTraceConfig} from '../base/trace_config_utils'; 16import {extractTraceConfig} from '../base/trace_config_utils'; 17import {isAdbTarget} from '../common/state'; 18 19import {Adb} from './adb_interfaces'; 20import {ReadBuffersResponse} from './consumer_port_types'; 21import {globals} from './globals'; 22import {Consumer, RpcConsumerPort} from './record_controller_interfaces'; 23 24export enum AdbConnectionState { 25 READY_TO_CONNECT, 26 AUTH_IN_PROGRESS, 27 CONNECTED, 28 CLOSED 29} 30 31interface Command { 32 method: string; 33 params: Uint8Array; 34} 35 36export abstract class AdbBaseConsumerPort extends RpcConsumerPort { 37 // Contains the commands sent while the authentication is in progress. They 38 // will all be executed afterwards. If the device disconnects, they are 39 // removed. 40 private commandQueue: Command[] = []; 41 42 protected adb: Adb; 43 protected state = AdbConnectionState.READY_TO_CONNECT; 44 protected device?: USBDevice; 45 46 protected constructor(adb: Adb, consumer: Consumer) { 47 super(consumer); 48 this.adb = adb; 49 } 50 51 async handleCommand(method: string, params: Uint8Array) { 52 try { 53 if (method === 'FreeBuffers') { 54 // When we finish tracing, we disconnect the adb device interface. 55 // Otherwise, we will keep holding the device interface and won't allow 56 // adb to access it. https://wicg.github.io/webusb/#abusing-a-device 57 // "Lastly, since USB devices are unable to distinguish requests from 58 // multiple sources, operating systems only allow a USB interface to 59 // have a single owning user-space or kernel-space driver." 60 this.state = AdbConnectionState.CLOSED; 61 await this.adb.disconnect(); 62 } else if (method === 'EnableTracing') { 63 this.state = AdbConnectionState.READY_TO_CONNECT; 64 } 65 66 if (this.state === AdbConnectionState.CLOSED) return; 67 68 this.commandQueue.push({method, params}); 69 70 if (this.state === AdbConnectionState.READY_TO_CONNECT || 71 this.deviceDisconnected()) { 72 this.state = AdbConnectionState.AUTH_IN_PROGRESS; 73 this.device = await this.findDevice(); 74 if (!this.device) { 75 this.state = AdbConnectionState.READY_TO_CONNECT; 76 const target = globals.state.recordingTarget; 77 throw Error(`Device with serial ${ 78 isAdbTarget(target) ? target.serial : 'n/a'} not found.`); 79 } 80 81 this.sendStatus(`Please allow USB debugging on device. 82 If you press cancel, reload the page.`); 83 84 await this.adb.connect(this.device); 85 86 // During the authentication the device may have been disconnected. 87 if (!globals.state.recordingInProgress || this.deviceDisconnected()) { 88 throw Error('Recording not in progress after adb authorization.'); 89 } 90 91 this.state = AdbConnectionState.CONNECTED; 92 this.sendStatus('Device connected.'); 93 } 94 95 if (this.state === AdbConnectionState.AUTH_IN_PROGRESS) return; 96 97 console.assert(this.state === AdbConnectionState.CONNECTED); 98 99 for (const cmd of this.commandQueue) this.invoke(cmd.method, cmd.params); 100 101 this.commandQueue = []; 102 } catch (e) { 103 this.commandQueue = []; 104 this.state = AdbConnectionState.READY_TO_CONNECT; 105 this.sendErrorMessage(e.message); 106 } 107 } 108 109 private deviceDisconnected() { 110 return !this.device || !this.device.opened; 111 } 112 113 setDurationStatus(enableTracingProto: Uint8Array) { 114 const traceConfigProto = extractTraceConfig(enableTracingProto); 115 if (!traceConfigProto) return; 116 const duration = extractDurationFromTraceConfig(traceConfigProto); 117 this.sendStatus(`Recording in progress${ 118 duration ? ' for ' + duration.toString() + ' ms' : ''}...`); 119 } 120 121 abstract invoke(method: string, argsProto: Uint8Array): void; 122 123 generateChunkReadResponse(data: Uint8Array, last = false): 124 ReadBuffersResponse { 125 return { 126 type: 'ReadBuffersResponse', 127 slices: [{data, lastSliceForPacket: last}] 128 }; 129 } 130 131 async findDevice(): Promise<USBDevice|undefined> { 132 if (!('usb' in navigator)) return undefined; 133 const connectedDevice = globals.state.recordingTarget; 134 if (!isAdbTarget(connectedDevice)) return undefined; 135 const devices = await navigator.usb.getDevices(); 136 return devices.find(d => d.serialNumber === connectedDevice.serial); 137 } 138}