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}