• 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
74function createInputListener(input_id, func, listener) {
75  input = document.getElementById(input_id);
76  if (func != null) {
77    input.oninput = func;
78  }
79  input.addEventListener('input', listener);
80}
81
82function validateMacAddress(val) {
83  var regex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;
84  return (regex.test(val));
85}
86
87function validateMacWrapper() {
88  let type = document.getElementById('bluetooth-wizard-type').value;
89  let button = document.getElementById("bluetooth-wizard-device");
90  let macField = document.getElementById('bluetooth-wizard-mac');
91  if (this.id == 'bluetooth-wizard-type') {
92    if (type == "remote_loopback") {
93      button.disabled = false;
94      macField.setCustomValidity('');
95      macField.disabled = true;
96      macField.required = false;
97      macField.placeholder = 'N/A';
98      macField.value = '';
99      return;
100    }
101  }
102  macField.disabled = false;
103  macField.required = true;
104  macField.placeholder = 'Device MAC';
105  if (validateMacAddress($(macField).val())) {
106    button.disabled = false;
107    macField.setCustomValidity('');
108  } else {
109    button.disabled = true;
110    macField.setCustomValidity('MAC address invalid');
111  }
112}
113
114$('[validate-mac]').bind('input', validateMacWrapper);
115$('[validate-mac]').bind('select', validateMacWrapper);
116
117function parseDevice(device) {
118  let id, name, mac;
119  var regex = /([0-9]+):([^@ ]*)(@(([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})))?/;
120  if (regex.test(device)) {
121    let regexMatches = device.match(regex);
122    id = regexMatches[1];
123    name = regexMatches[2];
124    mac = regexMatches[4];
125  }
126  if (mac === undefined) {
127    mac = "";
128  }
129  return [id, name, mac];
130}
131
132function btUpdateAdded(devices) {
133  let deviceArr = devices.split('\r\n');
134  let [id, name, mac] = parseDevice(deviceArr[0]);
135  if (name) {
136    let div = document.getElementById('bluetooth-wizard-confirm').getElementsByClassName('bluetooth-text')[1];
137    div.innerHTML = "";
138    div.innerHTML += "<p>Name: <b>" + id + "</b></p>";
139    div.innerHTML += "<p>Type: <b>" + name + "</b></p>";
140    div.innerHTML += "<p>MAC Addr: <b>" + mac + "</b></p>";
141    return true;
142  }
143  return false;
144}
145
146function parsePhy(phy) {
147  let id = phy.substring(0, phy.indexOf(":"));
148  phy = phy.substring(phy.indexOf(":") + 1);
149  let name = phy.substring(0, phy.indexOf(":"));
150  let devices = phy.substring(phy.indexOf(":") + 1);
151  return [id, name, devices];
152}
153
154function btParsePhys(phys) {
155  if (phys.indexOf("Phys:") < 0) {
156    return null;
157  }
158  let phyDict = {};
159  phys = phys.split('Phys:')[1];
160  let phyArr = phys.split('\r\n');
161  for (var phy of phyArr.slice(1)) {
162    phy = phy.trim();
163    if (phy.length == 0 || phy.indexOf("deleted") >= 0) {
164      continue;
165    }
166    let [id, name, devices] = parsePhy(phy);
167    phyDict[name] = id;
168  }
169  return phyDict;
170}
171
172function btUpdateDeviceList(devices) {
173  let deviceArr = devices.split('\r\n');
174  if (deviceArr[0].indexOf("Devices:") >= 0) {
175    let div = document.getElementById('bluetooth-list').getElementsByClassName('bluetooth-text')[0];
176    div.innerHTML = "";
177    let count = 0;
178    for (var device of deviceArr.slice(1)) {
179      if (device.indexOf("Phys:") >= 0) {
180        break;
181      }
182      count++;
183      if (device.indexOf("deleted") >= 0) {
184        continue;
185      }
186      let [id, name, mac] = parseDevice(device);
187      let innerDiv = '<div><button title="Delete" data-device-id="'
188      innerDiv += id;
189      innerDiv += '" class="bluetooth-list-trash material-icons">delete</button>';
190      innerDiv += name;
191      if (mac) {
192        innerDiv += " | "
193        innerDiv += mac;
194      }
195      innerDiv += '</div>';
196      div.innerHTML += innerDiv;
197    }
198    return count;
199  }
200  return -1;
201}
202
203function addMouseListeners(button, listener) {
204  // Capture mousedown/up/out commands instead of click to enable
205  // hold detection. mouseout is used to catch if the user moves the
206  // mouse outside the button while holding down.
207  button.addEventListener('mousedown', listener);
208  button.addEventListener('mouseup', listener);
209  button.addEventListener('mouseout', listener);
210}
211
212function createControlPanelButton(
213    title, icon_name, listener, parent_id = 'control-panel-default-buttons') {
214  let button = document.createElement('button');
215  document.getElementById(parent_id).appendChild(button);
216  button.title = title;
217  button.disabled = true;
218  addMouseListeners(button, listener);
219  // Set the button image using Material Design icons.
220  // See http://google.github.io/material-design-icons
221  // and https://material.io/resources/icons
222  button.classList.add('material-icons');
223  button.innerHTML = icon_name;
224  return button;
225}
226
227function positionModal(button_id, modal_id) {
228  const modalButton = document.getElementById(button_id);
229  const modalDiv = document.getElementById(modal_id);
230
231  // Position the modal to the right of the show modal button.
232  modalDiv.style.top = modalButton.offsetTop;
233  modalDiv.style.left = modalButton.offsetWidth + 30;
234}
235
236function createModalButton(button_id, modal_id, close_id, hide_id) {
237  const modalButton = document.getElementById(button_id);
238  const modalDiv = document.getElementById(modal_id);
239  const modalHeader = modalDiv.querySelector('.modal-header');
240  const modalClose = document.getElementById(close_id);
241  const modalDivHide = document.getElementById(hide_id);
242
243  positionModal(button_id, modal_id);
244
245  function showHideModal(show) {
246    if (show) {
247      modalButton.classList.add('modal-button-opened')
248      modalDiv.style.display = 'block';
249    } else {
250      modalButton.classList.remove('modal-button-opened')
251      modalDiv.style.display = 'none';
252    }
253    if (modalDivHide != null) {
254      modalDivHide.style.display = 'none';
255    }
256  }
257  // Allow the show modal button to toggle the modal,
258  modalButton.addEventListener(
259      'click', evt => showHideModal(modalDiv.style.display != 'block'));
260  // but the close button always closes.
261  modalClose.addEventListener('click', evt => showHideModal(false));
262
263  // Allow the modal to be dragged by the header.
264  let modalOffsets = {
265    midDrag: false,
266    mouseDownOffsetX: null,
267    mouseDownOffsetY: null,
268  };
269  modalHeader.addEventListener('mousedown', evt => {
270    modalOffsets.midDrag = true;
271    // Store the offset of the mouse location from the
272    // modal's current location.
273    modalOffsets.mouseDownOffsetX = parseInt(modalDiv.style.left) - evt.clientX;
274    modalOffsets.mouseDownOffsetY = parseInt(modalDiv.style.top) - evt.clientY;
275  });
276  modalHeader.addEventListener('mousemove', evt => {
277    let offsets = modalOffsets;
278    if (offsets.midDrag) {
279      // Move the modal to the mouse location plus the
280      // offset calculated on the initial mouse-down.
281      modalDiv.style.left = evt.clientX + offsets.mouseDownOffsetX;
282      modalDiv.style.top = evt.clientY + offsets.mouseDownOffsetY;
283    }
284  });
285  document.addEventListener('mouseup', evt => {
286    modalOffsets.midDrag = false;
287  });
288}
289
290function cmdConsole(consoleViewName, consoleInputName) {
291  let consoleView = document.getElementById(consoleViewName);
292
293  let addString =
294      function(str) {
295    consoleView.value += str;
296    consoleView.scrollTop = consoleView.scrollHeight;
297  }
298
299  let addLine =
300      function(line) {
301    addString(line + '\r\n');
302  }
303
304  let commandCallbacks = [];
305
306  let addCommandListener =
307      function(f) {
308    commandCallbacks.push(f);
309  }
310
311  let onCommand =
312      function(cmd) {
313    cmd = cmd.trim();
314
315    if (cmd.length == 0) return;
316
317    commandCallbacks.forEach(f => {
318      f(cmd);
319    })
320  }
321
322  addCommandListener(cmd => addLine('>> ' + cmd));
323
324  let consoleInput = document.getElementById(consoleInputName);
325
326  consoleInput.addEventListener('keydown', e => {
327    if ((e.key && e.key == 'Enter') || e.keyCode == 13) {
328      let command = e.target.value;
329
330      e.target.value = '';
331
332      onCommand(command);
333    }
334  });
335
336  return {
337    consoleView: consoleView,
338    consoleInput: consoleInput,
339    addLine: addLine,
340    addString: addString,
341    addCommandListener: addCommandListener,
342  };
343}
344