1/* 2 * Copyright (C) 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 { 18 ChangeDetectorRef, 19 Component, 20 EventEmitter, 21 Inject, 22 Input, 23 NgZone, 24 Output, 25 ViewEncapsulation, 26} from '@angular/core'; 27import {MatDialog} from '@angular/material/dialog'; 28import {assertDefined} from 'common/assert_utils'; 29import {FunctionUtils} from 'common/function_utils'; 30import {PersistentStoreProxy} from 'common/persistent_store_proxy'; 31import {Store} from 'common/store'; 32import {UserNotifier} from 'common/user_notifier'; 33import {Analytics} from 'logging/analytics'; 34import {ProgressListener} from 'messaging/progress_listener'; 35import {ProxyTraceTimeout} from 'messaging/user_warnings'; 36import { 37 NoTraceTargetsSelected, 38 WinscopeEvent, 39 WinscopeEventType, 40} from 'messaging/winscope_event'; 41import { 42 EmitEvent, 43 WinscopeEventEmitter, 44} from 'messaging/winscope_event_emitter'; 45import {WinscopeEventListener} from 'messaging/winscope_event_listener'; 46import {AdbConnection} from 'trace_collection/adb_connection'; 47import {AdbDevice} from 'trace_collection/adb_device'; 48import {AdbFiles, RequestedTraceTypes} from 'trace_collection/adb_files'; 49import {ConnectionState} from 'trace_collection/connection_state'; 50import {ProxyConnection} from 'trace_collection/proxy_connection'; 51import { 52 EnableConfiguration, 53 makeDefaultDumpConfigMap, 54 makeDefaultTraceConfigMap, 55 makeScreenRecordingConfigs, 56 SelectionConfiguration, 57 TraceConfigurationMap, 58} from 'trace_collection/trace_configuration'; 59import {TraceRequest, TraceRequestConfig} from 'trace_collection/trace_request'; 60import {LoadProgressComponent} from './load_progress_component'; 61import { 62 WarningDialogComponent, 63 WarningDialogData, 64 WarningDialogResult, 65} from './warning_dialog_component'; 66 67@Component({ 68 selector: 'collect-traces', 69 template: ` 70 <mat-card class="collect-card"> 71 <mat-card-title class="title">Collect Traces</mat-card-title> 72 73 <mat-card-content *ngIf="adbConnection" class="collect-card-content"> 74 <p *ngIf="adbConnection.getState() === ${ConnectionState.CONNECTING}" class="connecting-message mat-body-1"> 75 Connecting... 76 </p> 77 78 <div *ngIf="!adbSuccess()" class="set-up-adb"> 79 <button 80 class="proxy-tab" 81 color="primary" 82 mat-stroked-button 83 [ngClass]="tabClass(true)"> 84 ADB Proxy 85 </button> 86 <!-- <button class="web-tab" color="primary" mat-raised-button [ngClass]="tabClass(false)" (click)="displayWebAdbTab()">Web ADB</button> --> 87 <adb-proxy 88 *ngIf="isAdbProxy()" 89 [state]="adbConnection.getState()" 90 (retryConnection)="onRetryConnection($event)"></adb-proxy> 91 <!-- <web-adb *ngIf="!isAdbProxy()"></web-adb> TODO: fix web adb workflow --> 92 </div> 93 94 <div *ngIf="showAllDevices()" class="devices-connecting"> 95 <div *ngIf="adbConnection.getDevices().length === 0" class="no-device-detected"> 96 <p class="mat-body-3 icon"><mat-icon inline fontIcon="phonelink_erase"></mat-icon></p> 97 <p class="mat-body-1">No devices detected</p> 98 </div> 99 <div *ngIf="adbConnection.getDevices().length > 0" class="device-selection"> 100 <p class="mat-body-1 instruction">Select a device:</p> 101 <mat-list> 102 <mat-list-item 103 *ngFor="let device of adbConnection.getDevices()" 104 (click)="onDeviceClick(device)" 105 class="available-device"> 106 <mat-icon matListIcon> 107 {{ 108 device.authorized ? 'smartphone' : 'screen_lock_portrait' 109 }} 110 </mat-icon> 111 <p matLine> 112 {{ 113 device.authorized 114 ? device.model 115 : 'unauthorized' 116 }} 117 ({{ device.id }}) 118 </p> 119 </mat-list-item> 120 </mat-list> 121 </div> 122 </div> 123 124 <div 125 *ngIf="showTraceCollectionConfig()" 126 class="trace-collection-config"> 127 <mat-list> 128 <mat-list-item class="selected-device"> 129 <mat-icon matListIcon>smartphone</mat-icon> 130 <p matLine> 131 {{ getSelectedDevice()}} 132 </p> 133 134 <div class="device-actions"> 135 <button 136 color="primary" 137 class="change-btn" 138 mat-stroked-button 139 (click)="onChangeDeviceButton()" 140 [disabled]="isTracingOrLoading()"> 141 Change device 142 </button> 143 <button 144 color="primary" 145 class="fetch-btn" 146 mat-stroked-button 147 (click)="fetchExistingTraces()" 148 [disabled]="isTracingOrLoading()"> 149 Fetch traces from last session 150 </button> 151 </div> 152 </mat-list-item> 153 </mat-list> 154 155 <mat-tab-group [selectedIndex]="selectedTabIndex" class="tracing-tabs"> 156 <mat-tab 157 label="Trace" 158 [disabled]="disableTraceSection()"> 159 <div class="tabbed-section"> 160 <div class="trace-section" *ngIf="adbConnection.getState() === ${ConnectionState.IDLE}"> 161 <trace-config 162 title="Trace targets" 163 [initialTraceConfig]="traceConfig" 164 [storage]="storage" 165 traceConfigStoreKey="TraceSettings" 166 (traceConfigChange)="onTraceConfigChange($event)"></trace-config> 167 <div class="start-btn"> 168 <button color="primary" mat-raised-button (click)="startTracing()"> 169 Start trace 170 </button> 171 </div> 172 </div> 173 174 <div *ngIf="adbConnection.getState() === ${ConnectionState.STARTING_TRACE}" class="starting-trace"> 175 <load-progress 176 message="Starting trace..."> 177 </load-progress> 178 <div class="end-btn"> 179 <button color="primary" mat-raised-button [disabled]="true"> 180 End trace 181 </button> 182 </div> 183 </div> 184 185 <div *ngIf="adbConnection.getState() === ${ConnectionState.TRACING}" class="tracing"> 186 <load-progress 187 icon="cable" 188 message="Tracing..."> 189 </load-progress> 190 <div class="end-btn"> 191 <button color="primary" mat-raised-button (click)="endTrace()"> 192 End trace 193 </button> 194 </div> 195 </div> 196 197 <div *ngIf="adbConnection.getState() === ${ConnectionState.ENDING_TRACE}" class="ending-trace"> 198 <load-progress 199 icon="cable" 200 message="Ending trace..."> 201 </load-progress> 202 <div class="end-btn"> 203 <button color="primary" mat-raised-button [disabled]="true"> 204 End trace 205 </button> 206 </div> 207 </div> 208 209 <div *ngIf="isLoadOperationInProgress()" class="load-data"> 210 <load-progress 211 [progressPercentage]="progressPercentage" 212 [message]="progressMessage"> 213 </load-progress> 214 <div class="end-btn"> 215 <button color="primary" mat-raised-button [disabled]="true"> 216 End trace 217 </button> 218 </div> 219 </div> 220 </div> 221 </mat-tab> 222 <mat-tab label="Dump" [disabled]="isTracingOrLoading()"> 223 <div class="tabbed-section"> 224 <div class="dump-section" *ngIf="adbConnection.getState() === ${ConnectionState.IDLE} && !refreshDumps"> 225 <trace-config 226 title="Dump targets" 227 [initialTraceConfig]="dumpConfig" 228 [storage]="storage" 229 [traceConfigStoreKey]="storeKeyDumpConfig" 230 (traceConfigChange)="onDumpConfigChange($event)"></trace-config> 231 <div class="dump-btn" *ngIf="!refreshDumps"> 232 <button color="primary" mat-raised-button (click)="dumpState()"> 233 Dump state 234 </button> 235 </div> 236 </div> 237 238 <load-progress 239 class="dumping-state" 240 *ngIf="isDumpingState()" 241 [progressPercentage]="progressPercentage" 242 [message]="progressMessage"> 243 </load-progress> 244 </div> 245 </mat-tab> 246 </mat-tab-group> 247 </div> 248 249 <div *ngIf="adbConnection.getState() === ${ConnectionState.ERROR}" class="unknown-error"> 250 <p class="error-wrapper mat-body-1"> 251 <mat-icon class="error-icon">error</mat-icon> 252 Error: 253 </p> 254 <pre> {{ adbConnection.getErrorText() }} </pre> 255 <button color="primary" class="retry-btn" mat-raised-button (click)="onRetryButton()"> 256 Retry 257 </button> 258 </div> 259 </mat-card-content> 260 </mat-card> 261 `, 262 styles: [ 263 ` 264 .change-btn, 265 .retry-btn, 266 .fetch-btn { 267 margin-left: 5px; 268 } 269 .fetch-btn { 270 margin-top: 5px; 271 } 272 .selected-device { 273 height: fit-content !important; 274 } 275 .mat-card.collect-card { 276 display: flex; 277 } 278 .collect-card { 279 height: 100%; 280 flex-direction: column; 281 overflow: auto; 282 margin: 10px; 283 } 284 .collect-card-content { 285 overflow: auto; 286 } 287 .selection { 288 display: flex; 289 flex-direction: row; 290 flex-wrap: wrap; 291 gap: 10px; 292 } 293 .set-up-adb, 294 .trace-collection-config, 295 .trace-section, 296 .dump-section, 297 .starting-trace, 298 .tracing, 299 .ending-trace, 300 .load-data, 301 trace-config { 302 display: flex; 303 flex-direction: column; 304 gap: 10px; 305 } 306 .trace-section, 307 .dump-section, 308 .starting-trace, 309 .tracing, 310 .ending-trace, 311 .load-data { 312 height: 100%; 313 } 314 .trace-collection-config { 315 height: 100%; 316 } 317 .proxy-tab, 318 .web-tab, 319 .start-btn, 320 .dump-btn, 321 .end-btn { 322 align-self: flex-start; 323 } 324 .start-btn, 325 .dump-btn, 326 .end-btn { 327 margin: auto 0 0 0; 328 padding: 1rem 0 0 0; 329 } 330 .error-wrapper { 331 display: flex; 332 flex-direction: row; 333 align-items: center; 334 } 335 .error-icon { 336 margin-right: 5px; 337 } 338 .available-device { 339 cursor: pointer; 340 } 341 342 .no-device-detected { 343 display: flex; 344 flex-direction: column; 345 justify-content: center; 346 align-content: center; 347 align-items: center; 348 height: 100%; 349 } 350 351 .no-device-detected p, 352 .device-selection p.instruction { 353 padding-top: 1rem; 354 opacity: 0.6; 355 font-size: 1.2rem; 356 } 357 358 .no-device-detected .icon { 359 font-size: 3rem; 360 margin: 0 0 0.2rem 0; 361 } 362 363 .devices-connecting { 364 height: 100%; 365 } 366 367 mat-card-content { 368 flex-grow: 1; 369 } 370 371 mat-tab-body { 372 padding: 1rem; 373 } 374 375 .loading-info { 376 opacity: 0.8; 377 padding: 1rem 0; 378 } 379 380 .tracing-tabs { 381 flex-grow: 1; 382 } 383 384 .tracing-tabs .mat-tab-body-wrapper { 385 flex-grow: 1; 386 } 387 388 .tabbed-section { 389 height: 100%; 390 } 391 392 .progress-desc { 393 display: flex; 394 height: 100%; 395 flex-direction: column; 396 justify-content: center; 397 align-content: center; 398 align-items: center; 399 } 400 401 .progress-desc > * { 402 max-width: 250px; 403 } 404 405 load-progress { 406 height: 100%; 407 } 408 `, 409 ], 410 encapsulation: ViewEncapsulation.None, 411}) 412export class CollectTracesComponent 413 implements ProgressListener, WinscopeEventListener, WinscopeEventEmitter 414{ 415 objectKeys = Object.keys; 416 isExternalOperationInProgress = false; 417 progressMessage = 'Fetching...'; 418 progressPercentage: number | undefined; 419 lastUiProgressUpdateTimeMs?: number; 420 refreshDumps = false; 421 selectedTabIndex = 0; 422 traceConfig: TraceConfigurationMap; 423 dumpConfig: TraceConfigurationMap; 424 requestedTraceTypes: RequestedTraceTypes[] = []; 425 426 private readonly storeKeyImeWarning = 'doNotShowImeWarningDialog'; 427 private readonly storeKeyLastDevice = 'adb.lastDevice'; 428 private readonly storeKeyDumpConfig = 'DumpSettings'; 429 430 private selectedDevice: AdbDevice | undefined; 431 private emitEvent: EmitEvent = FunctionUtils.DO_NOTHING_ASYNC; 432 433 private readonly notConnected = [ 434 ConnectionState.NOT_FOUND, 435 ConnectionState.UNAUTH, 436 ConnectionState.INVALID_VERSION, 437 ]; 438 439 @Input() adbConnection: AdbConnection | undefined; 440 @Input() storage: Store | undefined; 441 442 @Output() readonly filesCollected = new EventEmitter<AdbFiles>(); 443 444 constructor( 445 @Inject(ChangeDetectorRef) private changeDetectorRef: ChangeDetectorRef, 446 @Inject(MatDialog) private dialog: MatDialog, 447 @Inject(NgZone) private ngZone: NgZone, 448 ) { 449 this.traceConfig = makeDefaultTraceConfigMap(); 450 this.dumpConfig = makeDefaultDumpConfigMap(); 451 } 452 453 ngOnChanges() { 454 if (!this.adbConnection) { 455 throw new Error('component created without adb connection'); 456 } 457 this.adbConnection.initialize( 458 () => this.onConnectionStateChange(), 459 this.toggleAvailabilityOfTraces, 460 this.handleDevicesChange, 461 ); 462 } 463 464 ngOnDestroy() { 465 this.adbConnection?.onDestroy(); 466 } 467 468 setEmitEvent(callback: EmitEvent) { 469 this.emitEvent = callback; 470 } 471 472 onDeviceClick(device: AdbDevice) { 473 this.selectedDevice = device; 474 this.storage?.add(this.storeKeyLastDevice, device.id); 475 this.changeDetectorRef.detectChanges(); 476 } 477 478 async onWinscopeEvent(event: WinscopeEvent) { 479 await event.visit( 480 WinscopeEventType.APP_REFRESH_DUMPS_REQUEST, 481 async (event) => { 482 this.selectedTabIndex = 1; 483 this.dumpConfig = PersistentStoreProxy.new<TraceConfigurationMap>( 484 assertDefined('DumpSettings'), 485 assertDefined( 486 JSON.parse(JSON.stringify(assertDefined(this.dumpConfig))), 487 ), 488 assertDefined(this.storage), 489 ); 490 this.refreshDumps = true; 491 }, 492 ); 493 } 494 495 onProgressUpdate(message: string, progressPercentage: number | undefined) { 496 if ( 497 !LoadProgressComponent.canUpdateComponent(this.lastUiProgressUpdateTimeMs) 498 ) { 499 return; 500 } 501 this.isExternalOperationInProgress = true; 502 this.progressMessage = message; 503 this.progressPercentage = progressPercentage; 504 this.lastUiProgressUpdateTimeMs = Date.now(); 505 this.changeDetectorRef.detectChanges(); 506 } 507 508 onOperationFinished(success: boolean) { 509 this.isExternalOperationInProgress = false; 510 this.lastUiProgressUpdateTimeMs = undefined; 511 if (!success) { 512 this.adbConnection?.restartConnection(); 513 } 514 this.changeDetectorRef.detectChanges(); 515 } 516 517 isLoadOperationInProgress(): boolean { 518 return ( 519 assertDefined(this.adbConnection).getState() === 520 ConnectionState.LOADING_DATA || this.isExternalOperationInProgress 521 ); 522 } 523 524 async onRetryConnection(token: string) { 525 const connection = assertDefined(this.adbConnection); 526 connection.setSecurityToken(token); 527 await connection.restartConnection(); 528 } 529 530 showAllDevices(): boolean { 531 const connection = assertDefined(this.adbConnection); 532 const state = connection.getState(); 533 if (state !== ConnectionState.IDLE) { 534 return false; 535 } 536 537 const devices = connection.getDevices(); 538 const lastId = this.storage?.get(this.storeKeyLastDevice) ?? undefined; 539 if ( 540 this.selectedDevice && 541 !devices.find((d) => d.id === this.selectedDevice?.id) 542 ) { 543 this.selectedDevice = undefined; 544 } 545 546 if (this.selectedDevice === undefined && lastId !== undefined) { 547 const device = devices.find((d) => d.id === lastId); 548 if (device && device.authorized) { 549 this.selectedDevice = device; 550 this.storage?.add(this.storeKeyLastDevice, device.id); 551 return false; 552 } 553 } 554 555 return this.selectedDevice === undefined; 556 } 557 558 showTraceCollectionConfig(): boolean { 559 if (this.selectedDevice === undefined) { 560 return false; 561 } 562 return ( 563 assertDefined(this.adbConnection).getState() === ConnectionState.IDLE || 564 this.isTracingOrLoading() 565 ); 566 } 567 568 onTraceConfigChange(newConfig: TraceConfigurationMap) { 569 this.traceConfig = newConfig; 570 } 571 572 onDumpConfigChange(newConfig: TraceConfigurationMap) { 573 this.dumpConfig = newConfig; 574 } 575 576 async onChangeDeviceButton() { 577 this.storage?.add(this.storeKeyLastDevice, ''); 578 this.selectedDevice = undefined; 579 await this.adbConnection?.restartConnection(); 580 } 581 582 async onRetryButton() { 583 await assertDefined(this.adbConnection).restartConnection(); 584 } 585 586 adbSuccess() { 587 const state = this.adbConnection?.getState(); 588 return !!state && !this.notConnected.includes(state); 589 } 590 591 async startTracing() { 592 const requestedTraces = this.getRequestedTraces(); 593 594 const imeReq = requestedTraces.includes('ime'); 595 const doNotShowDialog = !!this.storage?.get(this.storeKeyImeWarning); 596 597 if (!imeReq || doNotShowDialog) { 598 await this.requestTraces(requestedTraces); 599 return; 600 } 601 602 const sfReq = requestedTraces.includes('layers_trace'); 603 const transactionsReq = requestedTraces.includes('transactions'); 604 const wmReq = requestedTraces.includes('window_trace'); 605 const imeValidFrameMapping = sfReq && transactionsReq && wmReq; 606 607 if (imeValidFrameMapping) { 608 await this.requestTraces(requestedTraces); 609 return; 610 } 611 612 this.ngZone.run(() => { 613 const closeText = 'Collect traces anyway'; 614 const optionText = 'Do not show again'; 615 const data: WarningDialogData = { 616 message: `Cannot build frame mapping for IME with selected traces - some Winscope features may not work properly. 617 Consider the following selection for valid frame mapping: 618 Surface Flinger, Transactions, Window Manager, IME`, 619 actions: ['Go back'], 620 options: [optionText], 621 closeText, 622 }; 623 const dialogRef = this.dialog.open(WarningDialogComponent, { 624 data, 625 disableClose: true, 626 }); 627 dialogRef 628 .beforeClosed() 629 .subscribe((result: WarningDialogResult | undefined) => { 630 if (this.storage && result?.selectedOptions.includes(optionText)) { 631 this.storage.add(this.storeKeyImeWarning, 'true'); 632 } 633 if (result?.closeActionText === closeText) { 634 this.requestTraces(requestedTraces); 635 } 636 }); 637 }); 638 } 639 640 async dumpState() { 641 const requestedDumps = this.getRequestedDumps(); 642 const requestedTraceTypes = requestedDumps.map((req) => { 643 return { 644 name: this.dumpConfig[req].name, 645 types: this.dumpConfig[req].types, 646 }; 647 }); 648 Analytics.Tracing.logCollectDumps(requestedDumps); 649 650 if (requestedDumps.length === 0) { 651 this.emitEvent(new NoTraceTargetsSelected()); 652 return; 653 } 654 requestedDumps.push('perfetto_dump'); // always dump/fetch perfetto dump 655 656 const requestedDumpsWithConfig: TraceRequest[] = requestedDumps.map( 657 (dumpName) => { 658 const enabledConfig = this.requestedEnabledConfig( 659 dumpName, 660 this.dumpConfig, 661 ); 662 const selectedConfig = this.requestedSelectedConfig( 663 dumpName, 664 this.dumpConfig, 665 ); 666 return { 667 name: dumpName, 668 config: enabledConfig.concat(selectedConfig), 669 }; 670 }, 671 ); 672 673 this.progressMessage = 'Dumping state...'; 674 675 const connection = assertDefined(this.adbConnection); 676 const device = assertDefined(this.selectedDevice); 677 await connection.dumpState(device, requestedDumpsWithConfig); 678 this.refreshDumps = false; 679 if (connection.getState() === ConnectionState.DUMPING_STATE) { 680 this.filesCollected.emit({ 681 requested: requestedTraceTypes, 682 collected: await connection.fetchLastTracingSessionData(device), 683 }); 684 } 685 } 686 687 async endTrace() { 688 const connection = assertDefined(this.adbConnection); 689 const device = assertDefined(this.selectedDevice); 690 await connection.endTrace(device); 691 if (connection.getState() === ConnectionState.ENDING_TRACE) { 692 this.filesCollected.emit({ 693 requested: this.requestedTraceTypes, 694 collected: await connection.fetchLastTracingSessionData(device), 695 }); 696 } 697 } 698 699 isAdbProxy(): boolean { 700 return this.adbConnection instanceof ProxyConnection; 701 } 702 703 tabClass(adbTab: boolean) { 704 let isActive: string; 705 if (adbTab) { 706 isActive = this.isAdbProxy() ? 'active' : 'inactive'; 707 } else { 708 isActive = !this.isAdbProxy() ? 'active' : 'inactive'; 709 } 710 return ['tab', isActive]; 711 } 712 713 getSelectedDevice(): string { 714 const device = assertDefined(this.selectedDevice); 715 return device.model + `(${device.id})`; 716 } 717 718 isTracingOrLoading(): boolean { 719 const state = this.adbConnection?.getState(); 720 const tracingStates = [ 721 ConnectionState.STARTING_TRACE, 722 ConnectionState.TRACING, 723 ConnectionState.ENDING_TRACE, 724 ConnectionState.DUMPING_STATE, 725 ]; 726 return ( 727 (!!state && tracingStates.includes(state)) || 728 this.isLoadOperationInProgress() 729 ); 730 } 731 732 isDumpingState(): boolean { 733 return ( 734 this.refreshDumps || 735 this.adbConnection?.getState() === ConnectionState.DUMPING_STATE || 736 this.isLoadOperationInProgress() 737 ); 738 } 739 740 disableTraceSection(): boolean { 741 return this.isTracingOrLoading() || this.refreshDumps; 742 } 743 744 async fetchExistingTraces() { 745 const connection = assertDefined(this.adbConnection); 746 const files = await connection.fetchLastTracingSessionData( 747 assertDefined(this.selectedDevice), 748 ); 749 this.filesCollected.emit({ 750 requested: [], 751 collected: files, 752 }); 753 if (files.length === 0) { 754 await connection.restartConnection(); 755 } 756 } 757 758 private async requestTraces(requestedTraces: string[]) { 759 this.requestedTraceTypes = requestedTraces.map((req) => { 760 return { 761 name: this.traceConfig[req].name, 762 types: this.traceConfig[req].types, 763 }; 764 }); 765 Analytics.Tracing.logCollectTraces(requestedTraces); 766 767 if (requestedTraces.length === 0) { 768 this.emitEvent(new NoTraceTargetsSelected()); 769 return; 770 } 771 requestedTraces.push('perfetto_trace'); // always start/stop/fetch perfetto trace 772 773 const requestedTracesWithConfig: TraceRequest[] = requestedTraces.map( 774 (traceName) => { 775 const enabledConfig = this.requestedEnabledConfig( 776 traceName, 777 this.traceConfig, 778 ); 779 const selectedConfig = this.requestedSelectedConfig( 780 traceName, 781 this.traceConfig, 782 ); 783 return { 784 name: traceName, 785 config: enabledConfig.concat(selectedConfig), 786 }; 787 }, 788 ); 789 await assertDefined(this.adbConnection).startTrace( 790 assertDefined(this.selectedDevice), 791 requestedTracesWithConfig, 792 ); 793 } 794 795 private async onConnectionStateChange() { 796 this.changeDetectorRef.detectChanges(); 797 798 const connection = assertDefined(this.adbConnection); 799 const state = connection.getState(); 800 if (state === ConnectionState.TRACE_TIMEOUT) { 801 UserNotifier.add(new ProxyTraceTimeout()); 802 this.filesCollected.emit({ 803 requested: this.requestedTraceTypes, 804 collected: await connection.fetchLastTracingSessionData( 805 assertDefined(this.selectedDevice), 806 ), 807 }); 808 return; 809 } 810 811 if ( 812 !this.refreshDumps || 813 state === ConnectionState.LOADING_DATA || 814 state === ConnectionState.CONNECTING 815 ) { 816 return; 817 } 818 if (state === ConnectionState.IDLE && this.selectedDevice) { 819 this.dumpState(); 820 } else { 821 // device is not connected or proxy is not started/invalid/in error state 822 // so cannot refresh dump automatically 823 this.refreshDumps = false; 824 } 825 } 826 827 private getRequestedTraces(): string[] { 828 const tracingConfig = assertDefined(this.traceConfig); 829 return Object.keys(tracingConfig).filter((traceKey: string) => { 830 return tracingConfig[traceKey].enabled; 831 }); 832 } 833 834 private getRequestedDumps(): string[] { 835 let dumpConfig = assertDefined(this.dumpConfig); 836 if (this.refreshDumps && this.storage) { 837 const storedConfig = this.storage.get(this.storeKeyDumpConfig); 838 if (storedConfig) { 839 dumpConfig = JSON.parse(storedConfig); 840 } 841 } 842 return Object.keys(dumpConfig).filter((dumpKey: string) => { 843 return dumpConfig[dumpKey].enabled; 844 }); 845 } 846 847 private requestedEnabledConfig( 848 traceName: string, 849 configMap: TraceConfigurationMap, 850 ): TraceRequestConfig[] { 851 const req: TraceRequestConfig[] = []; 852 const trace = configMap[traceName]; 853 if (trace?.enabled) { 854 trace.config?.enableConfigs?.forEach((con: EnableConfiguration) => { 855 if (con.enabled) { 856 req.push({key: con.key}); 857 } 858 }); 859 } 860 return req; 861 } 862 863 private requestedSelectedConfig( 864 traceName: string, 865 configMap: TraceConfigurationMap, 866 ): TraceRequestConfig[] { 867 const trace = configMap[traceName]; 868 if (!trace?.enabled) { 869 return []; 870 } 871 return ( 872 trace.config?.selectionConfigs.map((con: SelectionConfiguration) => { 873 return {key: con.key, value: con.value}; 874 }) ?? [] 875 ); 876 } 877 878 private toggleAvailabilityOfTraces = (traces: string[]) => 879 traces.forEach((trace) => { 880 const config = assertDefined(this.traceConfig)[trace]; 881 config.available = !config.available; 882 }); 883 884 private handleDevicesChange = (devices: AdbDevice[]) => { 885 if (!this.selectedDevice) { 886 return; 887 } 888 const selectedDevice = devices.find( 889 (d) => d.id === assertDefined(this.selectedDevice).id, 890 ); 891 if (!selectedDevice) { 892 return; 893 } 894 const screenRecordingConfig = assertDefined( 895 this.traceConfig['screen_recording'].config, 896 ); 897 const displays = assertDefined( 898 screenRecordingConfig?.selectionConfigs.find((c) => c.key === 'displays'), 899 ); 900 if ( 901 selectedDevice.multiDisplayScreenRecordingAvailable && 902 !Array.isArray(displays.value) 903 ) { 904 screenRecordingConfig.selectionConfigs = makeScreenRecordingConfigs( 905 selectedDevice.displays, 906 [], 907 ); 908 } else if ( 909 !selectedDevice.multiDisplayScreenRecordingAvailable && 910 Array.isArray(displays.value) 911 ) { 912 screenRecordingConfig.selectionConfigs = makeScreenRecordingConfigs( 913 selectedDevice.displays, 914 '', 915 ); 916 } else { 917 screenRecordingConfig.selectionConfigs[0].options = 918 selectedDevice.displays; 919 } 920 921 const screenshotConfig = assertDefined(this.dumpConfig)['screenshot'] 922 .config; 923 assertDefined( 924 screenshotConfig?.selectionConfigs.find((c) => c.key === 'displays'), 925 ).options = selectedDevice.displays; 926 927 this.changeDetectorRef.detectChanges(); 928 }; 929} 930