• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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}