1/* 2 * Copyright 2022, The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17import {assertDefined} from 'common/assert_utils'; 18import {FunctionUtils, OnProgressUpdateType} from 'common/function_utils'; 19import {TimeUtils} from 'common/time_utils'; 20import { 21 DeviceProperties, 22 proxyClient, 23 ProxyEndpoint, 24 proxyRequest, 25 ProxyState, 26} from 'trace_collection/proxy_client'; 27import {Connection} from './connection'; 28import { 29 ConfigMap, 30 TraceConfigurationMap, 31 TRACES, 32} from './trace_collection_utils'; 33 34export class ProxyConnection implements Connection { 35 proxy = proxyClient; 36 keep_alive_worker: NodeJS.Timeout | undefined; 37 notConnected = [ 38 ProxyState.NO_PROXY, 39 ProxyState.UNAUTH, 40 ProxyState.INVALID_VERSION, 41 ]; 42 43 constructor( 44 private proxyStateChangeCallback: (state: ProxyState) => void, 45 private progressCallback: OnProgressUpdateType = FunctionUtils.DO_NOTHING, 46 private traceConfigChangeCallback: ( 47 availableTracesConfig: TraceConfigurationMap, 48 ) => void, 49 ) { 50 this.proxy.setState(ProxyState.CONNECTING); 51 this.proxy.onProxyChange( 52 async (newState) => await this.onConnectChange(newState), 53 ); 54 const urlParams = new URLSearchParams(window.location.search); 55 if (urlParams.has('token')) { 56 this.proxy.proxyKey = assertDefined(urlParams.get('token')); 57 } else if (this.proxy.store.get('adb.proxyKey')) { 58 this.proxy.proxyKey = assertDefined(this.proxy.store.get('adb.proxyKey')); 59 } 60 this.proxy.getDevices(); 61 } 62 63 devices() { 64 return this.proxy.devices; 65 } 66 67 adbData() { 68 return this.proxy.adbData; 69 } 70 71 state() { 72 return this.proxy.state; 73 } 74 75 isDevicesState() { 76 return this.state() === ProxyState.DEVICES; 77 } 78 79 isStartTraceState() { 80 return this.state() === ProxyState.START_TRACE; 81 } 82 83 isErrorState() { 84 return this.state() === ProxyState.ERROR; 85 } 86 87 isStartingTraceState() { 88 return this.state() === ProxyState.STARTING_TRACE; 89 } 90 91 isEndTraceState() { 92 return this.state() === ProxyState.END_TRACE; 93 } 94 95 isLoadDataState() { 96 return this.state() === ProxyState.LOAD_DATA; 97 } 98 99 isConnectingState() { 100 return this.state() === ProxyState.CONNECTING; 101 } 102 103 throwNoTargetsError() { 104 this.proxy.setState(ProxyState.ERROR, 'No targets selected'); 105 } 106 107 setProxyKey(key: string) { 108 this.proxy.proxyKey = key; 109 this.proxy.store.add('adb.proxyKey', key); 110 } 111 112 adbSuccess() { 113 return !this.notConnected.includes(this.proxy.state); 114 } 115 116 selectedDevice(): DeviceProperties { 117 return this.proxy.devices[this.proxy.selectedDevice]; 118 } 119 120 selectedDeviceId(): string { 121 return this.proxy.selectedDevice; 122 } 123 124 restart() { 125 this.proxy.setState(ProxyState.CONNECTING); 126 } 127 128 resetLastDevice() { 129 this.proxy.store.add('adb.lastDevice', ''); 130 this.restart(); 131 } 132 133 selectDevice(id: string) { 134 this.proxy.selectDevice(id); 135 } 136 137 keepAliveTrace(view: ProxyConnection) { 138 if (!view.isStartingTraceState() && !view.isEndTraceState()) { 139 clearInterval(view.keep_alive_worker); 140 view.keep_alive_worker = undefined; 141 return; 142 } 143 proxyRequest.keepTraceAlive(view.proxy, (request: XMLHttpRequest) => { 144 if (request.responseText !== 'True') { 145 view.endTrace(); 146 } else if (view.keep_alive_worker === undefined) { 147 view.keep_alive_worker = setInterval(view.keepAliveTrace, 1000, view); 148 } 149 }); 150 } 151 152 async startTrace( 153 requestedTraces: string[], 154 reqEnableConfig?: string[], 155 reqSelectedSfConfig?: ConfigMap, 156 reqSelectedWmConfig?: ConfigMap, 157 ) { 158 if (reqEnableConfig) { 159 proxyRequest.setEnabledConfig(this.proxy, reqEnableConfig); 160 } 161 if (reqSelectedSfConfig) { 162 proxyRequest.setSelectedConfig( 163 ProxyEndpoint.SELECTED_SF_CONFIG_TRACE, 164 this.proxy, 165 reqSelectedSfConfig, 166 ); 167 } 168 if (reqSelectedWmConfig) { 169 proxyRequest.setSelectedConfig( 170 ProxyEndpoint.SELECTED_WM_CONFIG_TRACE, 171 this.proxy, 172 reqSelectedWmConfig, 173 ); 174 } 175 await proxyClient.setState(ProxyState.STARTING_TRACE); 176 await proxyRequest.startTrace( 177 this.proxy, 178 requestedTraces, 179 (request: XMLHttpRequest) => this.keepAliveTrace(this), 180 ); 181 // TODO(b/330118129): identify source of additional start latency that affects some traces 182 await TimeUtils.sleepMs(1000); // 1s timeout ensures SR fully started 183 proxyClient.setState(ProxyState.END_TRACE); 184 } 185 186 async endTrace() { 187 this.progressCallback(0); 188 await this.proxy.setState(ProxyState.LOAD_DATA); 189 await proxyRequest.endTrace(this.proxy, this.progressCallback); 190 } 191 192 async dumpState(requestedDumps: string[]): Promise<boolean> { 193 this.progressCallback(0); 194 if (requestedDumps.length < 1) { 195 console.error('No targets selected'); 196 await this.proxy.setState(ProxyState.ERROR, 'No targets selected'); 197 return false; 198 } 199 await this.proxy.setState(ProxyState.LOAD_DATA); 200 await proxyRequest.dumpState( 201 this.proxy, 202 requestedDumps, 203 this.progressCallback, 204 ); 205 return true; 206 } 207 208 isWaylandAvailable(): Promise<boolean> { 209 return new Promise((resolve, reject) => { 210 proxyRequest.call( 211 'GET', 212 ProxyEndpoint.CHECK_WAYLAND, 213 (request: XMLHttpRequest) => { 214 resolve(request.responseText === 'true'); 215 }, 216 ); 217 }); 218 } 219 220 async onConnectChange(newState: ProxyState) { 221 if (newState === ProxyState.CONNECTING) { 222 proxyClient.getDevices(); 223 } 224 if (newState === ProxyState.START_TRACE) { 225 const isWaylandAvailable = await this.isWaylandAvailable(); 226 if (isWaylandAvailable) { 227 const availableTracesConfig = TRACES['default']; 228 if (isWaylandAvailable) { 229 Object.assign(availableTracesConfig, TRACES['arc']); 230 } 231 this.traceConfigChangeCallback(availableTracesConfig); 232 } 233 } 234 this.proxyStateChangeCallback(newState); 235 } 236} 237