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 m from 'mithril'; 16 17import {RecordingPageController} from '../../common/recordingV2/recording_page_controller'; 18import { 19 EXTENSION_URL, 20 RECORDING_MODAL_DIALOG_KEY, 21} from '../../common/recordingV2/recording_utils'; 22import { 23 CHROME_TARGET_FACTORY, 24 ChromeTargetFactory, 25} from '../../common/recordingV2/target_factories/chrome_target_factory'; 26import {targetFactoryRegistry} from '../../common/recordingV2/target_factory_registry'; 27import {WebsocketMenuController} from '../../common/recordingV2/websocket_menu_controller'; 28import {closeModal, showModal} from '../../widgets/modal'; 29import {CodeSnippet} from '../record_widgets'; 30 31import {RecordingMultipleChoice} from './recording_multiple_choice'; 32 33const RUN_WEBSOCKET_CMD = 34 '# Get tracebox\n' + 35 'curl -LO https://get.perfetto.dev/tracebox\n' + 36 'chmod +x ./tracebox\n' + 37 '# Option A - trace android devices\n' + 38 'adb start-server\n' + 39 '# Option B - trace the host OS\n' + 40 './tracebox traced --background\n' + 41 './tracebox traced_probes --background\n' + 42 '# Start the websocket server\n' + 43 './tracebox websocket_bridge\n'; 44 45export function showAddNewTargetModal(controller: RecordingPageController) { 46 showModal({ 47 title: 'Add new recording target', 48 key: RECORDING_MODAL_DIALOG_KEY, 49 content: () => 50 m( 51 '.record-modal', 52 m('text', 'Select platform:'), 53 assembleWebusbSection(controller), 54 m('.line'), 55 assembleWebsocketSection(controller), 56 m('.line'), 57 assembleChromeSection(controller), 58 ), 59 }); 60} 61 62function assembleWebusbSection( 63 recordingPageController: RecordingPageController, 64): m.Vnode { 65 return m( 66 '.record-modal-section', 67 m('.logo-wrapping', m('i.material-icons', 'usb')), 68 m( 69 '.record-modal-description', 70 m('h3', 'Android device over WebUSB'), 71 m( 72 'text', 73 'Android developers: this option cannot co-operate ' + 74 'with the adb host on your machine. Only one entity between ' + 75 'the browser and adb can control the USB endpoint. If adb is ' + 76 'running, you will be prompted to re-assign the device to the ' + 77 'browser. Use the websocket option below to use both ' + 78 'simultaneously.', 79 ), 80 m( 81 '.record-modal-button', 82 { 83 onclick: () => { 84 closeModal(RECORDING_MODAL_DIALOG_KEY); 85 recordingPageController.addAndroidDevice(); 86 }, 87 }, 88 'Connect new WebUSB driver', 89 ), 90 ), 91 ); 92} 93 94function assembleWebsocketSection( 95 recordingPageController: RecordingPageController, 96): m.Vnode { 97 const websocketComponents = []; 98 websocketComponents.push( 99 m('h3', 'Android / Linux / MacOS device via Websocket'), 100 ); 101 websocketComponents.push( 102 m( 103 'text', 104 'This option assumes that the adb server is already ' + 105 'running on your machine.', 106 ), 107 m( 108 '.record-modal-command', 109 m(CodeSnippet, { 110 text: RUN_WEBSOCKET_CMD, 111 }), 112 ), 113 ); 114 115 websocketComponents.push( 116 m( 117 '.record-modal-command', 118 m('text', 'Websocket bridge address: '), 119 m('input[type=text]', { 120 value: websocketMenuController.getPath(), 121 oninput() { 122 websocketMenuController.setPath(this.value); 123 }, 124 }), 125 m( 126 '.record-modal-logo-button', 127 { 128 onclick: () => websocketMenuController.onPathChange(), 129 }, 130 m('i.material-icons', 'refresh'), 131 ), 132 ), 133 ); 134 135 websocketComponents.push( 136 m(RecordingMultipleChoice, { 137 controller: recordingPageController, 138 targetFactories: websocketMenuController.getTargetFactories(), 139 }), 140 ); 141 142 return m( 143 '.record-modal-section', 144 m('.logo-wrapping', m('i.material-icons', 'settings_ethernet')), 145 m('.record-modal-description', ...websocketComponents), 146 ); 147} 148 149function assembleChromeSection( 150 recordingPageController: RecordingPageController, 151): m.Vnode | undefined { 152 if (!targetFactoryRegistry.has(CHROME_TARGET_FACTORY)) { 153 return undefined; 154 } 155 156 const chromeComponents = []; 157 chromeComponents.push(m('h3', 'Chrome Browser instance or ChromeOS device')); 158 159 const chromeFactory: ChromeTargetFactory = targetFactoryRegistry.get( 160 CHROME_TARGET_FACTORY, 161 ) as ChromeTargetFactory; 162 163 if (!chromeFactory.isExtensionInstalled) { 164 chromeComponents.push( 165 m( 166 'text', 167 'Install the extension ', 168 m('a', {href: EXTENSION_URL, target: '_blank'}, 'from this link '), 169 'and refresh the page.', 170 ), 171 ); 172 } else { 173 chromeComponents.push( 174 m(RecordingMultipleChoice, { 175 controller: recordingPageController, 176 targetFactories: [chromeFactory], 177 }), 178 ); 179 } 180 181 return m( 182 '.record-modal-section', 183 m('.logo-wrapping', m('i.material-icons', 'web')), 184 m('.record-modal-description', ...chromeComponents), 185 ); 186} 187 188const websocketMenuController = new WebsocketMenuController(); 189