• 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 {exists} from '../base/utils';
16import {isAdbTarget} from '../common/state';
17import {
18  extractDurationFromTraceConfig,
19  extractTraceConfig,
20} from '../core/trace_config_utils';
21import {globals} from '../frontend/globals';
22
23import {Adb} from './adb_interfaces';
24import {ReadBuffersResponse} from './consumer_port_types';
25import {Consumer, RpcConsumerPort} from './record_controller_interfaces';
26
27export enum AdbConnectionState {
28  READY_TO_CONNECT,
29  AUTH_IN_PROGRESS,
30  CONNECTED,
31  CLOSED,
32}
33
34interface Command {
35  method: string;
36  params: Uint8Array;
37}
38
39export abstract class AdbBaseConsumerPort extends RpcConsumerPort {
40  // Contains the commands sent while the authentication is in progress. They
41  // will all be executed afterwards. If the device disconnects, they are
42  // removed.
43  private commandQueue: Command[] = [];
44
45  protected adb: Adb;
46  protected state = AdbConnectionState.READY_TO_CONNECT;
47  protected device?: USBDevice;
48
49  protected constructor(adb: Adb, consumer: Consumer) {
50    super(consumer);
51    this.adb = adb;
52  }
53
54  async handleCommand(method: string, params: Uint8Array) {
55    try {
56      if (method === 'FreeBuffers') {
57        // When we finish tracing, we disconnect the adb device interface.
58        // Otherwise, we will keep holding the device interface and won't allow
59        // adb to access it. https://wicg.github.io/webusb/#abusing-a-device
60        // "Lastly, since USB devices are unable to distinguish requests from
61        // multiple sources, operating systems only allow a USB interface to
62        // have a single owning user-space or kernel-space driver."
63        this.state = AdbConnectionState.CLOSED;
64        await this.adb.disconnect();
65      } else if (method === 'EnableTracing') {
66        this.state = AdbConnectionState.READY_TO_CONNECT;
67      }
68
69      if (this.state === AdbConnectionState.CLOSED) return;
70
71      this.commandQueue.push({method, params});
72
73      if (
74        this.state === AdbConnectionState.READY_TO_CONNECT ||
75        this.deviceDisconnected()
76      ) {
77        this.state = AdbConnectionState.AUTH_IN_PROGRESS;
78        this.device = await this.findDevice();
79        if (!this.device) {
80          this.state = AdbConnectionState.READY_TO_CONNECT;
81          const target = globals.state.recordingTarget;
82          throw Error(
83            `Device with serial ${
84              isAdbTarget(target) ? target.serial : 'n/a'
85            } not found.`,
86          );
87        }
88
89        this.sendStatus(`Please allow USB debugging on device.
90          If you press cancel, reload the page.`);
91
92        await this.adb.connect(this.device);
93
94        // During the authentication the device may have been disconnected.
95        if (!globals.state.recordingInProgress || this.deviceDisconnected()) {
96          throw Error('Recording not in progress after adb authorization.');
97        }
98
99        this.state = AdbConnectionState.CONNECTED;
100        this.sendStatus('Device connected.');
101      }
102
103      if (this.state === AdbConnectionState.AUTH_IN_PROGRESS) return;
104
105      console.assert(this.state === AdbConnectionState.CONNECTED);
106
107      for (const cmd of this.commandQueue) this.invoke(cmd.method, cmd.params);
108
109      this.commandQueue = [];
110    } catch (e) {
111      this.commandQueue = [];
112      this.state = AdbConnectionState.READY_TO_CONNECT;
113      this.sendErrorMessage(e.message);
114    }
115  }
116
117  private deviceDisconnected() {
118    return !this.device || !this.device.opened;
119  }
120
121  setDurationStatus(enableTracingProto: Uint8Array) {
122    const traceConfigProto = extractTraceConfig(enableTracingProto);
123    if (!traceConfigProto) return;
124    const duration = extractDurationFromTraceConfig(traceConfigProto);
125    this.sendStatus(
126      `Recording in progress${
127        exists(duration) ? ' for ' + duration.toString() + ' ms' : ''
128      }...`,
129    );
130  }
131
132  abstract invoke(method: string, argsProto: Uint8Array): void;
133
134  generateChunkReadResponse(
135    data: Uint8Array,
136    last = false,
137  ): ReadBuffersResponse {
138    return {
139      type: 'ReadBuffersResponse',
140      slices: [{data, lastSliceForPacket: last}],
141    };
142  }
143
144  async findDevice(): Promise<USBDevice | undefined> {
145    if (!('usb' in navigator)) return undefined;
146    const connectedDevice = globals.state.recordingTarget;
147    if (!isAdbTarget(connectedDevice)) return undefined;
148    const devices = await navigator.usb.getDevices();
149    return devices.find((d) => d.serialNumber === connectedDevice.serial);
150  }
151}
152