• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 LocalStore from '../localstore.js';
18import {FILE_DECODERS, FILE_TYPES} from '../decode.js';
19
20export enum ProxyState {
21  ERROR = 0,
22  CONNECTING = 1,
23  NO_PROXY = 2,
24  INVALID_VERSION = 3,
25  UNAUTH = 4,
26  DEVICES = 5,
27  START_TRACE = 6,
28  END_TRACE = 7,
29  LOAD_DATA = 8,
30};
31
32export enum ProxyEndpoint {
33  DEVICES = '/devices/',
34  START_TRACE = '/start/',
35  END_TRACE = '/end/',
36  CONFIG_TRACE = '/configtrace/',
37  SELECTED_WM_CONFIG_TRACE = '/selectedwmconfigtrace/',
38  SELECTED_SF_CONFIG_TRACE = '/selectedsfconfigtrace/',
39  DUMP = '/dump/',
40  FETCH = '/fetch/',
41  STATUS = '/status/',
42  CHECK_WAYLAND = '/checkwayland/',
43};
44
45const proxyFileTypeAdapter = {
46  'window_trace': FILE_TYPES.WINDOW_MANAGER_TRACE,
47  'accessibility_trace': FILE_TYPES.ACCESSIBILITY_TRACE,
48  'layers_trace': FILE_TYPES.SURFACE_FLINGER_TRACE,
49  'wl_trace': FILE_TYPES.WAYLAND_TRACE,
50  'layers_dump': FILE_TYPES.SURFACE_FLINGER_DUMP,
51  'window_dump': FILE_TYPES.WINDOW_MANAGER_DUMP,
52  'wl_dump': FILE_TYPES.WAYLAND_DUMP,
53  'screen_recording': FILE_TYPES.SCREEN_RECORDING,
54  'transactions': FILE_TYPES.TRANSACTIONS_TRACE,
55  'transactions_legacy': FILE_TYPES.TRANSACTIONS_TRACE_LEGACY,
56  'proto_log': FILE_TYPES.PROTO_LOG,
57  'system_ui_trace': FILE_TYPES.SYSTEM_UI,
58  'launcher_trace': FILE_TYPES.LAUNCHER,
59  'ime_trace_clients': FILE_TYPES.IME_TRACE_CLIENTS,
60  'ime_trace_service': FILE_TYPES.IME_TRACE_SERVICE,
61  'ime_trace_managerservice': FILE_TYPES.IME_TRACE_MANAGERSERVICE,
62};
63
64class ProxyClient {
65  readonly WINSCOPE_PROXY_URL = 'http://localhost:5544';
66  readonly VERSION = '0.8';
67
68  store:LocalStore = LocalStore('adb', {
69    proxyKey: '',
70    lastDevice: ''});
71
72  state:ProxyState = ProxyState.CONNECTING;
73  stateChangeListeners:{(param:ProxyState, errorText:String): void;}[] = [];
74  refresh_worker: NodeJS.Timer = null;
75  devices = {};
76  selectedDevice = ""
77  errorText:String = ""
78
79  call(method, path, view, onSuccess, type = null, jsonRequest = null) {
80    const request = new XMLHttpRequest();
81    let client = this;
82    request.onreadystatechange = function() {
83      if (this.readyState !== 4) {
84        return;
85      }
86      if (this.status === 0) {
87        client.setState(ProxyState.NO_PROXY);
88      } else if (this.status === 200) {
89        if (this.getResponseHeader('Winscope-Proxy-Version') !== client.VERSION) {
90          client.setState(ProxyState.INVALID_VERSION);
91        } else if (onSuccess) {
92          onSuccess(this, view);
93        }
94      } else if (this.status === 403) {
95        client.setState(ProxyState.UNAUTH);
96      } else {
97        if (this.responseType === 'text' || !this.responseType) {
98          client.errorText = this.responseText;
99        } else if (this.responseType === 'arraybuffer') {
100          client.errorText = String.fromCharCode.apply(null, new Uint8Array(this.response));
101        }
102        client.setState(ProxyState.ERROR);
103      }
104    };
105    request.responseType = type || "";
106    request.open(method, client.WINSCOPE_PROXY_URL + path);
107    request.setRequestHeader('Winscope-Token', client.store.proxyKey);
108    if (jsonRequest) {
109      const json = JSON.stringify(jsonRequest);
110      request.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
111      request.send(json);
112    } else {
113      request.send();
114    }
115  }
116
117  setState(state:ProxyState, errorText:String = "") {
118    this.state = state;
119    this.errorText = errorText;
120    for (let listener of this.stateChangeListeners) {
121      listener(state, errorText);
122    }
123  }
124
125  onStateChange(fn: (state:ProxyState, errorText:String) => void) {
126    this.removeOnStateChange(fn);
127    this.stateChangeListeners.push(fn);
128  }
129
130  removeOnStateChange(removeFn: (state:ProxyState, errorText:String) => void) {
131    this.stateChangeListeners = this.stateChangeListeners.filter(fn => fn !== removeFn);
132  }
133
134  getDevices() {
135    if (this.state !== ProxyState.DEVICES && this.state !== ProxyState.CONNECTING) {
136      clearInterval(this.refresh_worker);
137      this.refresh_worker = null;
138      return;
139    }
140    let client = this;
141    this.call('GET', ProxyEndpoint.DEVICES, this, function(request, view) {
142      try {
143        client.devices = JSON.parse(request.responseText);
144        if (client.store.lastDevice && client.devices[client.store.lastDevice] &&
145              client.devices[client.store.lastDevice].authorised) {
146          client.selectDevice(client.store.lastDevice);
147        } else {
148          if (client.refresh_worker === null) {
149            client.refresh_worker = setInterval(client.getDevices, 1000);
150          }
151          client.setState(ProxyState.DEVICES);
152        }
153      } catch (err) {
154        console.error(err);
155        client.errorText = request.responseText;
156        client.setState(ProxyState.ERROR);
157      }
158    });
159  }
160
161  selectDevice(device_id) {
162    this.selectedDevice = device_id;
163    this.store.lastDevice = device_id;
164    this.setState(ProxyState.START_TRACE);
165  }
166
167  deviceId() {
168    return this.selectedDevice;
169  }
170
171  resetLastDevice() {
172    this.store.lastDevice = '';
173  }
174
175  loadFile(files, idx, traceType, view) {
176    let client = this;
177    this.call('GET', `${ProxyEndpoint.FETCH}${proxyClient.deviceId()}/${files[idx]}/`, view,
178        (request, view) => {
179      try {
180        const enc = new TextDecoder('utf-8');
181        const resp = enc.decode(request.response);
182        const filesByType = JSON.parse(resp);
183
184        for (const filetype in filesByType) {
185          if (filesByType.hasOwnProperty(filetype)) {
186            const files = filesByType[filetype];
187            const fileDecoder = FILE_DECODERS[proxyFileTypeAdapter[filetype]];
188
189            for (const encodedFileBuffer of files) {
190              const buffer = Uint8Array.from(atob(encodedFileBuffer), (c) => c.charCodeAt(0));
191              const data = fileDecoder.decoder(buffer, fileDecoder.decoderParams,
192                  fileDecoder.name, view.store);
193              view.dataFiles.push(data);
194              view.loadProgress = 100 * (idx + 1) / files.length; // TODO: Update this
195            }
196          }
197        }
198
199        if (idx < files.length - 1) {
200          client.loadFile(files, idx + 1, traceType, view);
201        } else {
202          const currentDate = new Date().toISOString();
203          view.$emit('dataReady',
204              `winscope-${traceType}-${currentDate}`,
205              view.dataFiles);
206        }
207      } catch (err) {
208        console.error(err);
209        client.setState(ProxyState.ERROR, err);
210      }
211    }, 'arraybuffer');
212  }
213
214}
215
216export const proxyClient = new ProxyClient();
217