• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2021 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
17// Creates a "toggle control". The onToggleCb callback is called every time the
18// control changes state with the new toggle position (true for ON) and is
19// expected to return a promise of the new toggle position which can resolve to
20// the opposite position of the one received if there was error.
21function createToggleControl(elm, onToggleCb, initialState = false) {
22  elm.classList.add('toggle-control');
23  let offClass = 'toggle-off';
24  let onClass = 'toggle-on';
25  let state = !!initialState;
26  let toggle = {
27    // Sets the state of the toggle control. This only affects the
28    // visible state of the control in the UI, it doesn't affect the
29    // state of the underlying resources. It's most useful to make
30    // changes of said resources visible to the user.
31    Set: enabled => {
32      state = enabled;
33      if (enabled) {
34        elm.classList.remove(offClass);
35        elm.classList.add(onClass);
36      } else {
37        elm.classList.add(offClass);
38        elm.classList.remove(onClass);
39      }
40    }
41  };
42  toggle.Set(initialState);
43  addMouseListeners(elm, e => {
44    if (e.type != 'mousedown') {
45      return;
46    }
47    // Enable it if it's currently disabled
48    let enableNow = !state;
49    let nextPr = onToggleCb(enableNow);
50    if (nextPr && 'then' in nextPr) {
51      nextPr.then(enabled => toggle.Set(enabled));
52    }
53  });
54  return toggle;
55}
56
57function createButtonListener(button_id_class, func,
58  deviceConnection, listener) {
59  let buttons = [];
60  let ele = document.getElementById(button_id_class);
61  if (ele != null) {
62    buttons.push(ele);
63  } else {
64    buttons = document.getElementsByClassName(button_id_class);
65  }
66  for (var button of buttons) {
67    if (func != null) {
68      button.onclick = func;
69    }
70    button.addEventListener('mousedown', listener);
71  }
72}
73
74// Bind the update of slider value to slider input,
75// and trigger a function to be called on input change  and slider stop.
76function createSliderListener(slider_class, listener) {
77  const sliders = document.getElementsByClassName(slider_class + '-range');
78  const values = document.getElementsByClassName(slider_class + '-value');
79
80  for (let i = 0; i < sliders.length; i++) {
81    let slider = sliders[i];
82    let value = values[i];
83    // Trigger value update when the slider value changes while sliding.
84    slider.addEventListener('input', () => {
85      value.textContent = slider.value;
86      listener();
87    });
88    // Trigger value update when the slider stops sliding.
89    slider.addEventListener('change', () => {
90      listener();
91    });
92
93  }
94}
95
96function createInputListener(input_id, func, listener) {
97  input = document.getElementById(input_id);
98  if (func != null) {
99    input.oninput = func;
100  }
101  input.addEventListener('input', listener);
102}
103
104function createSelectListener(select_id, listener) {
105  select = document.getElementById(select_id);
106  select.addEventListener('change', listener);
107}
108
109function validateMacAddress(val) {
110  var regex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;
111  return (regex.test(val));
112}
113
114function validateMacWrapper() {
115  let type = document.getElementById('bluetooth-wizard-type').value;
116  let button = document.getElementById("bluetooth-wizard-device");
117  let macField = document.getElementById('bluetooth-wizard-mac');
118  if (this.id == 'bluetooth-wizard-type') {
119    if (type == "remote_loopback") {
120      button.disabled = false;
121      macField.setCustomValidity('');
122      macField.disabled = true;
123      macField.required = false;
124      macField.placeholder = 'N/A';
125      macField.value = '';
126      return;
127    }
128  }
129  macField.disabled = false;
130  macField.required = true;
131  macField.placeholder = 'Device MAC';
132  if (validateMacAddress($(macField).val())) {
133    button.disabled = false;
134    macField.setCustomValidity('');
135  } else {
136    button.disabled = true;
137    macField.setCustomValidity('MAC address invalid');
138  }
139}
140
141$('[validate-mac]').bind('input', validateMacWrapper);
142$('[validate-mac]').bind('select', validateMacWrapper);
143
144function parseDevice(device) {
145  let id, name, mac;
146  var regex = /([0-9]+):([^@ ]*)(@(([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})))?/;
147  if (regex.test(device)) {
148    let regexMatches = device.match(regex);
149    id = regexMatches[1];
150    name = regexMatches[2];
151    mac = regexMatches[4];
152  }
153  if (mac === undefined) {
154    mac = "";
155  }
156  return [id, name, mac];
157}
158
159function btUpdateAdded(devices) {
160  let deviceArr = devices.split('\r\n');
161  let [id, name, mac] = parseDevice(deviceArr[0]);
162  if (name) {
163    let div = document.getElementById('bluetooth-wizard-confirm').getElementsByClassName('bluetooth-text')[1];
164    div.innerHTML = "";
165    div.innerHTML += "<p>Name: <b>" + id + "</b></p>";
166    div.innerHTML += "<p>Type: <b>" + name + "</b></p>";
167    div.innerHTML += "<p>MAC Addr: <b>" + mac + "</b></p>";
168    return true;
169  }
170  return false;
171}
172
173function parsePhy(phy) {
174  let id = phy.substring(0, phy.indexOf(":"));
175  phy = phy.substring(phy.indexOf(":") + 1);
176  let name = phy.substring(0, phy.indexOf(":"));
177  let devices = phy.substring(phy.indexOf(":") + 1);
178  return [id, name, devices];
179}
180
181function btParsePhys(phys) {
182  if (phys.indexOf("Phys:") < 0) {
183    return null;
184  }
185  let phyDict = {};
186  phys = phys.split('Phys:')[1];
187  let phyArr = phys.split('\r\n');
188  for (var phy of phyArr.slice(1)) {
189    phy = phy.trim();
190    if (phy.length == 0 || phy.indexOf("deleted") >= 0) {
191      continue;
192    }
193    let [id, name, devices] = parsePhy(phy);
194    phyDict[name] = id;
195  }
196  return phyDict;
197}
198
199function btUpdateDeviceList(devices) {
200  let deviceArr = devices.split('\r\n');
201  if (deviceArr[0].indexOf("Devices:") >= 0) {
202    let div = document.getElementById('bluetooth-list').getElementsByClassName('bluetooth-text')[0];
203    div.innerHTML = "";
204    let count = 0;
205    for (var device of deviceArr.slice(1)) {
206      if (device.indexOf("Phys:") >= 0) {
207        break;
208      }
209      count++;
210      if (device.indexOf("deleted") >= 0) {
211        continue;
212      }
213      let [id, name, mac] = parseDevice(device);
214      let innerDiv = '<div><button title="Delete" data-device-id="'
215      innerDiv += id;
216      innerDiv += '" class="bluetooth-list-trash material-icons">delete</button>';
217      innerDiv += name;
218      if (mac) {
219        innerDiv += " | "
220        innerDiv += mac;
221      }
222      innerDiv += '</div>';
223      div.innerHTML += innerDiv;
224    }
225    return count;
226  }
227  return -1;
228}
229
230function addMouseListeners(button, listener) {
231  // Capture mousedown/up/out commands instead of click to enable
232  // hold detection. mouseout is used to catch if the user moves the
233  // mouse outside the button while holding down.
234  button.addEventListener('mousedown', listener);
235  button.addEventListener('mouseup', listener);
236  button.addEventListener('mouseout', listener);
237}
238
239function createControlPanelButton(
240    title, icon_name, listener, parent_id = 'control-panel-default-buttons') {
241  let button = document.createElement('button');
242  document.getElementById(parent_id).appendChild(button);
243  button.title = title;
244  button.disabled = true;
245  addMouseListeners(button, listener);
246  // Set the button image using Material Design icons.
247  // See http://google.github.io/material-design-icons
248  // and https://material.io/resources/icons
249  button.classList.add('material-icons');
250  button.innerHTML = icon_name;
251  return button;
252}
253
254function positionModal(button_id, modal_id) {
255  const modalButton = document.getElementById(button_id);
256  const modalDiv = document.getElementById(modal_id);
257
258  // Position the modal to the right of the show modal button.
259  modalDiv.style.top = modalButton.offsetTop;
260  modalDiv.style.left = modalButton.offsetWidth + 30;
261}
262
263function createModalButton(button_id, modal_id, close_id, hide_id) {
264  const modalButton = document.getElementById(button_id);
265  const modalDiv = document.getElementById(modal_id);
266  const modalHeader = modalDiv.querySelector('.modal-header');
267  const modalClose = document.getElementById(close_id);
268  const modalDivHide = document.getElementById(hide_id);
269
270  positionModal(button_id, modal_id);
271
272  function showHideModal(show) {
273    if (show) {
274      modalButton.classList.add('modal-button-opened')
275      modalDiv.style.display = 'block';
276    } else {
277      modalButton.classList.remove('modal-button-opened')
278      modalDiv.style.display = 'none';
279    }
280    if (modalDivHide != null) {
281      modalDivHide.style.display = 'none';
282    }
283  }
284  // Allow the show modal button to toggle the modal,
285  modalButton.addEventListener(
286      'click', evt => showHideModal(modalDiv.style.display != 'block'));
287  // but the close button always closes.
288  modalClose.addEventListener('click', evt => showHideModal(false));
289
290  // Allow the modal to be dragged by the header.
291  let modalOffsets = {
292    midDrag: false,
293    mouseDownOffsetX: null,
294    mouseDownOffsetY: null,
295  };
296  modalHeader.addEventListener('mousedown', evt => {
297    modalOffsets.midDrag = true;
298    // Store the offset of the mouse location from the
299    // modal's current location.
300    modalOffsets.mouseDownOffsetX = parseInt(modalDiv.style.left) - evt.clientX;
301    modalOffsets.mouseDownOffsetY = parseInt(modalDiv.style.top) - evt.clientY;
302  });
303  modalHeader.addEventListener('mousemove', evt => {
304    let offsets = modalOffsets;
305    if (offsets.midDrag) {
306      // Move the modal to the mouse location plus the
307      // offset calculated on the initial mouse-down.
308      modalDiv.style.left = evt.clientX + offsets.mouseDownOffsetX;
309      modalDiv.style.top = evt.clientY + offsets.mouseDownOffsetY;
310    }
311  });
312  document.addEventListener('mouseup', evt => {
313    modalOffsets.midDrag = false;
314  });
315}
316
317function cmdConsole(consoleViewName, consoleInputName) {
318  let consoleView = document.getElementById(consoleViewName);
319
320  let addString =
321      function(str) {
322    consoleView.value += str;
323    consoleView.scrollTop = consoleView.scrollHeight;
324  }
325
326  let addLine =
327      function(line) {
328    addString(line + '\r\n');
329  }
330
331  let commandCallbacks = [];
332
333  let addCommandListener =
334      function(f) {
335    commandCallbacks.push(f);
336  }
337
338  let onCommand =
339      function(cmd) {
340    cmd = cmd.trim();
341
342    if (cmd.length == 0) return;
343
344    commandCallbacks.forEach(f => {
345      f(cmd);
346    })
347  }
348
349  addCommandListener(cmd => addLine('>> ' + cmd));
350
351  let consoleInput = document.getElementById(consoleInputName);
352
353  consoleInput.addEventListener('keydown', e => {
354    if ((e.key && e.key == 'Enter') || e.keyCode == 13) {
355      let command = e.target.value;
356
357      e.target.value = '';
358
359      onCommand(command);
360    }
361  });
362
363  return {
364    consoleView: consoleView,
365    consoleInput: consoleInput,
366    addLine: addLine,
367    addString: addString,
368    addCommandListener: addCommandListener,
369  };
370}
371