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/extract_utils'; 16import {extractTraceConfig} from '../base/extract_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 AdbAuthState { 25 DISCONNECTED, 26 AUTH_IN_PROGRESS, 27 CONNECTED, 28} 29 30interface Command { 31 method: string; 32 params: Uint8Array; 33} 34 35export abstract class AdbBaseConsumerPort extends RpcConsumerPort { 36 // Contains the commands sent while the authentication is in progress. They 37 // will all be executed afterwards. If the device disconnects, they are 38 // removed. 39 private commandQueue: Command[] = []; 40 41 protected adb: Adb; 42 protected state = AdbAuthState.DISCONNECTED; 43 protected device?: USBDevice; 44 45 constructor(adb: Adb, consumer: Consumer) { 46 super(consumer); 47 this.adb = adb; 48 } 49 50 async handleCommand(method: string, params: Uint8Array) { 51 try { 52 this.commandQueue.push({method, params}); 53 if (this.state === AdbAuthState.DISCONNECTED || 54 this.deviceDisconnected()) { 55 this.state = AdbAuthState.AUTH_IN_PROGRESS; 56 this.device = await this.findDevice(); 57 if (!this.device) { 58 this.state = AdbAuthState.DISCONNECTED; 59 const target = globals.state.recordingTarget; 60 throw Error(`Device with serial ${ 61 isAdbTarget(target) ? target.serial : 'n/a'} not found.`); 62 } 63 64 this.sendStatus(`Please allow USB debugging on device. 65 If you press cancel, reload the page.`); 66 67 await this.adb.connect(this.device); 68 69 // During the authentication the device may have been disconnected. 70 if (!globals.state.recordingInProgress || this.deviceDisconnected()) { 71 throw Error('Recording not in progress after adb authorization.'); 72 } 73 74 this.state = AdbAuthState.CONNECTED; 75 this.sendStatus('Device connected.'); 76 } 77 78 if (this.state === AdbAuthState.AUTH_IN_PROGRESS) return; 79 80 console.assert(this.state === AdbAuthState.CONNECTED); 81 82 for (const cmd of this.commandQueue) this.invoke(cmd.method, cmd.params); 83 84 this.commandQueue = []; 85 } catch (e) { 86 this.commandQueue = []; 87 this.state = AdbAuthState.DISCONNECTED; 88 this.sendErrorMessage(e.message); 89 } 90 } 91 92 private deviceDisconnected() { 93 return !this.device || !this.device.opened; 94 } 95 96 setDurationStatus(enableTracingProto: Uint8Array) { 97 const traceConfigProto = extractTraceConfig(enableTracingProto); 98 if (!traceConfigProto) return; 99 const duration = extractDurationFromTraceConfig(traceConfigProto); 100 this.sendStatus(`Recording in progress${ 101 duration ? ' for ' + duration.toString() + ' ms' : ''}...`); 102 } 103 104 abstract invoke(method: string, argsProto: Uint8Array): void; 105 106 generateChunkReadResponse(data: Uint8Array, last = false): 107 ReadBuffersResponse { 108 return { 109 type: 'ReadBuffersResponse', 110 slices: [{data, lastSliceForPacket: last}] 111 }; 112 } 113 114 async findDevice(): Promise<USBDevice|undefined> { 115 if (!('usb' in navigator)) return undefined; 116 const connectedDevice = globals.state.recordingTarget; 117 if (!isAdbTarget(connectedDevice)) return undefined; 118 const devices = await navigator.usb.getDevices(); 119 return devices.find(d => d.serialNumber === connectedDevice.serial); 120 } 121}