1// Copyright (C) 2022 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 {_TextDecoder} from 'custom_utils'; 16 17import {defer} from '../../base/deferred'; 18import {ArrayBufferBuilder} from '../array_buffer_builder'; 19 20import {AdbFileHandler} from './adb_file_handler'; 21import { 22 AdbConnection, 23 ByteStream, 24 OnDisconnectCallback, 25 OnMessageCallback, 26} from './recording_interfaces_v2'; 27 28const textDecoder = new _TextDecoder(); 29 30export abstract class AdbConnectionImpl implements AdbConnection { 31 // onStatus and onDisconnect are set to callbacks passed from the caller. 32 // This happens for instance in the AndroidWebusbTarget, which instantiates 33 // them with callbacks passed from the UI. 34 onStatus: OnMessageCallback = () => {}; 35 onDisconnect: OnDisconnectCallback = (_) => {}; 36 37 // Starts a shell command, and returns a promise resolved when the command 38 // completes. 39 async shellAndWaitCompletion(cmd: string): Promise<void> { 40 const adbStream = await this.shell(cmd); 41 const onStreamingEnded = defer<void>(); 42 43 // We wait for the stream to be closed by the device, which happens 44 // after the shell command is successfully received. 45 adbStream.addOnStreamCloseCallback(() => { 46 onStreamingEnded.resolve(); 47 }); 48 return onStreamingEnded; 49 } 50 51 // Starts a shell command, then gathers all its output and returns it as 52 // a string. 53 async shellAndGetOutput(cmd: string): Promise<string> { 54 const adbStream = await this.shell(cmd); 55 const commandOutput = new ArrayBufferBuilder(); 56 const onStreamingEnded = defer<string>(); 57 58 adbStream.addOnStreamDataCallback((data: Uint8Array) => { 59 commandOutput.append(data); 60 }); 61 adbStream.addOnStreamCloseCallback(() => { 62 onStreamingEnded.resolve( 63 textDecoder.decode(commandOutput.toArrayBuffer())); 64 }); 65 return onStreamingEnded; 66 } 67 68 async push(binary: Uint8Array, path: string): Promise<void> { 69 const byteStream = await this.openStream('sync:'); 70 await (new AdbFileHandler(byteStream)).pushBinary(binary, path); 71 // We need to wait until the bytestream is closed. Otherwise, we can have a 72 // race condition: 73 // If this is the last stream, it will try to disconnect the device. In the 74 // meantime, the caller might create another stream which will try to open 75 // the device. 76 await byteStream.closeAndWaitForTeardown(); 77 } 78 79 abstract shell(cmd: string): Promise<ByteStream>; 80 81 abstract canConnectWithoutContention(): Promise<boolean>; 82 83 abstract connectSocket(path: string): Promise<ByteStream>; 84 85 abstract disconnect(disconnectMessage?: string): Promise<void>; 86 87 protected abstract openStream(destination: string): Promise<ByteStream>; 88} 89