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 */
16import {
17  Component,
18  ElementRef,
19  EventEmitter,
20  Inject,
21  Input,
22  Output,
23} from '@angular/core';
24import {EMPTY_OBJ_STRING} from 'trace/tree_node/formatters';
25import {HierarchyTreeNode} from 'trace/tree_node/hierarchy_tree_node';
26import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
27import {TreeNode} from 'trace/tree_node/tree_node';
28import {ImeAdditionalProperties} from 'viewers/common/ime_additional_properties';
29import {
30  ImeContainerProperties,
31  InputMethodSurfaceProperties,
32} from 'viewers/common/ime_utils';
33import {ViewerEvents} from 'viewers/common/viewer_events';
34import {selectedElementStyle} from './styles/selected_element.styles';
35import {viewerCardInnerStyle} from './styles/viewer_card.styles';
36
37@Component({
38  selector: 'ime-additional-properties',
39  template: `
40    <div class="title-section">
41      <collapsible-section-title
42        class="view-header"
43        title="WM & SF PROPERTIES"
44        (collapseButtonClicked)="collapseButtonClicked.emit()"></collapsible-section-title>
45    </div>
46
47    <span class="mat-body-1 placeholder-text" *ngIf="!additionalProperties"> No IME entry found. </span>
48
49    <div class="additional-properties-content" *ngIf="additionalProperties">
50      <div *ngIf="isAllPropertiesUndefined()" class="group">
51        <p class="mat-body-1">
52          There is no corresponding WM / SF additionalProperties for this IME entry – no WM / SF
53          entry is recorded before this IME entry in time. View later frames for WM & SF properties.
54        </p>
55      </div>
56
57      <ng-container *ngIf="isImeManagerService">
58        <div class="group ime-manager-service">
59          <button
60            *ngIf="wmHierarchyTree()"
61            [color]="getButtonColor(wmHierarchyTree())"
62            mat-button
63            class="group-header"
64            [class]="{selected: isHighlighted(wmHierarchyTree())}"
65            (click)="onClickShowInPropertiesPanelWm(wmHierarchyTree(), 'Window Manager State')">
66            WMState
67          </button>
68          <h3 *ngIf="!wmHierarchyTree()" class="group-header mat-subheading-2">WMState</h3>
69          <div class="left-column wm-state">
70            <p *ngIf="additionalProperties?.wm" class="mat-body-1">
71              {{ wmRootLabel() }}
72            </p>
73            <p *ngIf="!additionalProperties?.wm" class="mat-body-1">
74              There is no corresponding WMState entry.
75            </p>
76          </div>
77        </div>
78        <div *ngIf="wmInsetsSourceProvider()" class="group insets-source-provider">
79          <button
80            [color]="getButtonColor(wmInsetsSourceProvider())"
81            mat-button
82            class="group-header"
83            [class]="{selected: isHighlighted(wmInsetsSourceProvider())}"
84            (click)="
85              onClickShowInPropertiesPanelWm(wmInsetsSourceProvider(), 'Ime Insets Source Provider')
86            ">
87            IME Insets Source Provider
88          </button>
89          <div class="left-column">
90            <p class="mat-body-2">Source Frame:</p>
91            <coordinates-table
92              [coordinates]="wmInsetsSourceProviderSourceFrame()"></coordinates-table>
93            <p class="mat-body-1">
94              <span class="mat-body-2">Source Visible:</span>
95              &ngsp;
96              {{ wmInsetsSourceProviderSourceVisible() }}
97            </p>
98            <p class="mat-body-2">Source Visible Frame:</p>
99            <coordinates-table
100              [coordinates]="wmInsetsSourceProviderSourceVisibleFrame()"></coordinates-table>
101            <p class="mat-body-1">
102              <span class="mat-body-2">Position:</span>
103              &ngsp;
104              {{ wmInsetsSourceProviderPosition() }}
105            </p>
106            <p class="mat-body-1">
107              <span class="mat-body-2">IsLeashReadyForDispatching:</span>
108              &ngsp;
109              {{ wmInsetsSourceProviderIsLeashReady() }}
110            </p>
111            <p class="mat-body-1">
112              <span class="mat-body-2">Controllable:</span>
113              &ngsp;
114              {{ wmInsetsSourceProviderControllable() }}
115            </p>
116          </div>
117        </div>
118        <div *ngIf="wmImeControlTarget()" class="group ime-control-target">
119          <button
120          [color]="getButtonColor(wmImeControlTarget())"
121            mat-button
122            class="group-header ime-control-target-button"
123            [class]="{selected: isHighlighted(wmImeControlTarget())}"
124            (click)="onClickShowInPropertiesPanelWm(wmImeControlTarget(), 'Ime Control Target')">
125            IME Control Target
126          </button>
127          <div class="left-column">
128            <p *ngIf="wmImeControlTargetTitle()" class="mat-body-1">
129              <span class="mat-body-2">Title:</span>
130              &ngsp;
131              {{ wmImeControlTargetTitle() }}
132            </p>
133          </div>
134        </div>
135        <div *ngIf="wmImeInputTarget()" class="group ime-input-target">
136          <button
137          [color]="getButtonColor(wmImeInputTarget())"
138            mat-button
139            class="group-header"
140            [class]="{selected: isHighlighted(wmImeInputTarget())}"
141            (click)="onClickShowInPropertiesPanelWm(wmImeInputTarget(), 'Ime Input Target')">
142            IME Input Target
143          </button>
144          <div class="left-column">
145            <p *ngIf="wmImeInputTargetTitle()" class="mat-body-1">
146              <span class="mat-body-2">Title:</span>
147              &ngsp;
148              {{ wmImeInputTargetTitle() }}
149            </p>
150          </div>
151        </div>
152        <div *ngIf="wmImeLayeringTarget()" class="group ime-layering-target">
153          <button
154          [color]="getButtonColor(wmImeLayeringTarget())"
155            mat-button
156            class="group-header"
157            [class]="{selected: isHighlighted(wmImeLayeringTarget())}"
158            (click)="onClickShowInPropertiesPanelWm(wmImeLayeringTarget(), 'Ime Layering Target')">
159            IME Layering Target
160          </button>
161          <div class="left-column">
162            <p *ngIf="wmImeLayeringTargetTitle()" class="mat-body-1">
163              <span class="mat-body-2">Title:</span>
164              &ngsp;
165              {{ wmImeLayeringTargetTitle() }}
166            </p>
167          </div>
168        </div>
169      </ng-container>
170
171      <ng-container *ngIf="!isImeManagerService">
172        <!-- Ime Client or Ime Service -->
173        <div class="group">
174          <button
175            *ngIf="wmHierarchyTree()"
176            [color]="getButtonColor(wmHierarchyTree())"
177            mat-button
178            class="group-header wm-state-button"
179            [class]="{selected: isHighlighted(wmHierarchyTree())}"
180            (click)="onClickShowInPropertiesPanelWm(wmHierarchyTree(), 'Window Manager State')">
181            WMState
182          </button>
183          <h3 *ngIf="!wmHierarchyTree()" class="group-header mat-subheading-2">WMState</h3>
184          <div class="left-column wm-state">
185            <p *ngIf="additionalProperties?.wm" class="mat-body-1">
186              {{ wmRootLabel() }}
187            </p>
188            <p *ngIf="!additionalProperties?.wm" class="mat-body-1">
189              There is no corresponding WMState entry.
190            </p>
191          </div>
192        </div>
193        <div class="group">
194          <h3 class="group-header mat-subheading-2">SFLayer</h3>
195          <div class="left-column sf-state">
196            <p *ngIf="additionalProperties?.sf" class="mat-body-1">
197              {{ sfRootLabel() }}
198            </p>
199            <p *ngIf="!additionalProperties?.sf" class="mat-body-1">
200              There is no corresponding SFLayer entry.
201            </p>
202          </div>
203        </div>
204        <div *ngIf="additionalProperties?.wm" class="group focus">
205          <h3 class="group-header mat-subheading-2">Focus</h3>
206          <div class="left-column">
207            <p class="mat-body-1">
208              <span class="mat-body-2">Focused App:</span>
209              &ngsp;
210              {{ additionalProperties.wm.wmStateProperties.focusedApp }}
211            </p>
212            <p class="mat-body-1">
213              <span class="mat-body-2">Focused Activity:</span>
214              &ngsp;
215              {{ additionalProperties.wm.wmStateProperties.focusedActivity }}
216            </p>
217            <p class="mat-body-1">
218              <span class="mat-body-2">Focused Window:</span>
219              &ngsp;
220              {{ additionalProperties.wm.wmStateProperties.focusedWindow ?? 'null' }}
221            </p>
222            <p *ngIf="additionalProperties.sf" class="mat-body-1">
223              <span class="mat-body-2">Focused Window Color:</span>
224              &ngsp;
225              {{ formattedWindowColor() }}
226            </p>
227            <p class="mat-body-2">Input Control Target Frame:</p>
228            <coordinates-table [coordinates]="wmControlTargetFrame()"></coordinates-table>
229          </div>
230        </div>
231        <div class="group visibility">
232          <h3 class="group-header mat-subheading-2">Visibility</h3>
233          <div class="left-column">
234            <p *ngIf="additionalProperties?.wm" class="mat-body-1">
235              <span class="mat-body-2">InputMethod Window:</span>
236              &ngsp;
237              {{ additionalProperties.wm.wmStateProperties.isInputMethodWindowVisible }}
238            </p>
239            <p *ngIf="additionalProperties?.sf" class="mat-body-1">
240              <span class="mat-body-2">InputMethod Surface:</span>
241              &ngsp;
242              {{ additionalProperties.sf.properties.inputMethodSurface?.isVisible ?? false }}
243            </p>
244          </div>
245        </div>
246        <div *ngIf="additionalProperties?.sf" class="group ime-container">
247          <button
248          [color]="getButtonColor(additionalProperties.sf.properties.imeContainer)"
249            mat-button
250            class="group-header ime-container-button"
251            [class]="{selected: isHighlighted(additionalProperties.sf.properties.imeContainer)}"
252            (click)="
253              onClickShowInPropertiesPanelSf(additionalProperties.sf.properties.imeContainer)
254            ">
255            Ime Container
256          </button>
257          <div class="left-column">
258            <p class="mat-body-1">
259              <span class="mat-body-2">ZOrderRelativeOfId:</span>
260              &ngsp;
261              {{ additionalProperties.sf.properties.imeContainer.zOrderRelativeOfId }}
262            </p>
263            <p class="mat-body-1">
264              <span class="mat-body-2">Z:</span>
265              &ngsp;
266              {{ additionalProperties.sf.properties.imeContainer.z }}
267            </p>
268          </div>
269        </div>
270        <div *ngIf="additionalProperties?.sf" class="group input-method-surface">
271          <button
272          [color]="getButtonColor(additionalProperties.sf.properties.inputMethodSurface)"
273            mat-button
274            class="group-header input-method-surface-button"
275            [class]="{
276              selected: isHighlighted(additionalProperties.sf.properties.inputMethodSurface)
277            }"
278            (click)="
279              onClickShowInPropertiesPanelSf(additionalProperties.sf.properties.inputMethodSurface)
280            ">
281            Input Method Surface
282          </button>
283          <div class="left-column">
284            <p class="mat-body-2">Screen Bounds:</p>
285            <coordinates-table [coordinates]="sfImeContainerScreenBounds()"></coordinates-table>
286          </div>
287          <div class="right-column">
288            <p class="mat-body-2">Rect:</p>
289            <coordinates-table [coordinates]="sfImeContainerRect()"></coordinates-table>
290          </div>
291        </div>
292      </ng-container>
293    </div>
294  `,
295  styles: [
296    `
297      :host collapsible-section-title {
298        padding-bottom: 8px;
299      }
300
301      .additional-properties-content {
302        height: 0;
303        flex-grow: 1;
304        overflow-y: auto;
305      }
306
307      .group {
308        padding: 8px;
309        display: flex;
310        flex-direction: row;
311        border-bottom: 1px solid var(--border-color);
312      }
313
314      .mat-body-1 {
315        overflow-wrap: anywhere;
316      }
317
318      .group-header {
319        height: 100%;
320        width: 80px;
321        padding: 0;
322        text-align: center;
323        line-height: normal;
324        white-space: normal;
325      }
326
327      p.group-header {
328        color: gray;
329      }
330
331      .left-column {
332        flex: 1;
333        padding: 0 5px;
334      }
335
336      .right-column {
337        flex: 1;
338        padding: 0 5px;
339      }
340    `,
341    selectedElementStyle,
342    viewerCardInnerStyle,
343  ],
344})
345export class ImeAdditionalPropertiesComponent {
346  @Input() additionalProperties: ImeAdditionalProperties | undefined;
347  @Input() isImeManagerService: boolean | undefined;
348  @Input() highlightedItem: string = '';
349
350  @Output() collapseButtonClicked = new EventEmitter();
351
352  constructor(@Inject(ElementRef) private elementRef: ElementRef) {}
353
354  isHighlighted(
355    item:
356      | TreeNode
357      | ImeContainerProperties
358      | InputMethodSurfaceProperties
359      | undefined,
360  ): boolean {
361    return item ? item.id === this.highlightedItem : false;
362  }
363
364  getButtonColor(node: TreeNode | undefined) {
365    return this.isHighlighted(node) ? undefined : 'primary';
366  }
367
368  formattedWindowColor(): string {
369    const color = this.additionalProperties?.sf?.properties.focusedWindowColor;
370    if (!color) return EMPTY_OBJ_STRING;
371    return color.formattedValue();
372  }
373
374  sfRootLabel(): string {
375    const rootProps = this.additionalProperties?.sf?.properties.root;
376    if (!rootProps) {
377      return this.additionalProperties?.sf?.name ?? 'root';
378    }
379
380    return rootProps.timestamp;
381  }
382
383  wmRootLabel(): string {
384    const timestamp =
385      this.additionalProperties?.wm?.wmStateProperties.timestamp;
386    if (!timestamp) {
387      return this.additionalProperties?.wm?.name ?? 'root';
388    }
389    return timestamp;
390  }
391
392  wmHierarchyTree(): HierarchyTreeNode | undefined {
393    return this.additionalProperties?.wm?.hierarchyTree;
394  }
395
396  wmInsetsSourceProvider(): PropertyTreeNode | undefined {
397    return this.additionalProperties?.wm?.wmStateProperties
398      .imeInsetsSourceProvider;
399  }
400
401  wmControlTargetFrame(): PropertyTreeNode | undefined {
402    return this.additionalProperties?.wm?.wmStateProperties.imeInsetsSourceProvider
403      ?.getChildByName('insetsSourceProvider')
404      ?.getChildByName('controlTarget')
405      ?.getChildByName('windowFrames')
406      ?.getChildByName('frame');
407  }
408
409  wmInsetsSourceProviderPosition(): string {
410    return (
411      this.additionalProperties?.wm?.wmStateProperties.imeInsetsSourceProvider
412        ?.getChildByName('insetsSourceProvider')
413        ?.getChildByName('control')
414        ?.getChildByName('position')
415        ?.formattedValue() ?? 'null'
416    );
417  }
418
419  wmInsetsSourceProviderIsLeashReady(): string {
420    return (
421      this.additionalProperties?.wm?.wmStateProperties.imeInsetsSourceProvider
422        ?.getChildByName('insetsSourceProvider')
423        ?.getChildByName('isLeashReadyForDispatching')
424        ?.formattedValue() ?? 'null'
425    );
426  }
427
428  wmInsetsSourceProviderControllable(): string {
429    return (
430      this.additionalProperties?.wm?.wmStateProperties.imeInsetsSourceProvider
431        ?.getChildByName('insetsSourceProvider')
432        ?.getChildByName('controllable')
433        ?.formattedValue() ?? 'null'
434    );
435  }
436
437  wmInsetsSourceProviderSourceFrame(): PropertyTreeNode | undefined {
438    return this.additionalProperties?.wm?.wmStateProperties.imeInsetsSourceProvider
439      ?.getChildByName('source')
440      ?.getChildByName('frame');
441  }
442
443  wmInsetsSourceProviderSourceVisible(): string {
444    return (
445      this.additionalProperties?.wm?.wmStateProperties.imeInsetsSourceProvider
446        ?.getChildByName('source')
447        ?.getChildByName('visible')
448        ?.formattedValue() ?? 'null'
449    );
450  }
451
452  wmInsetsSourceProviderSourceVisibleFrame(): PropertyTreeNode | undefined {
453    return this.additionalProperties?.wm?.wmStateProperties.imeInsetsSourceProvider
454      ?.getChildByName('source')
455      ?.getChildByName('visibleFrame');
456  }
457
458  wmImeControlTarget(): PropertyTreeNode | undefined {
459    return this.additionalProperties?.wm?.wmStateProperties.imeControlTarget;
460  }
461
462  wmImeControlTargetTitle(): string | undefined {
463    return (
464      this.additionalProperties?.wm?.wmStateProperties.imeControlTarget
465        ?.getChildByName('windowContainer')
466        ?.getChildByName('identifier')
467        ?.getChildByName('title')
468        ?.formattedValue() ?? undefined
469    );
470  }
471
472  wmImeInputTarget(): PropertyTreeNode | undefined {
473    return this.additionalProperties?.wm?.wmStateProperties.imeInputTarget;
474  }
475
476  wmImeInputTargetTitle(): string | undefined {
477    return (
478      this.additionalProperties?.wm?.wmStateProperties.imeInputTarget
479        ?.getChildByName('windowContainer')
480        ?.getChildByName('identifier')
481        ?.getChildByName('title')
482        ?.formattedValue() ?? undefined
483    );
484  }
485
486  wmImeLayeringTarget(): PropertyTreeNode | undefined {
487    return this.additionalProperties?.wm?.wmStateProperties.imeLayeringTarget;
488  }
489
490  wmImeLayeringTargetTitle(): string | undefined {
491    return (
492      this.additionalProperties?.wm?.wmStateProperties.imeLayeringTarget
493        ?.getChildByName('windowContainer')
494        ?.getChildByName('identifier')
495        ?.getChildByName('title')
496        ?.formattedValue() ?? undefined
497    );
498  }
499
500  sfImeContainerScreenBounds(): PropertyTreeNode | undefined {
501    return (
502      this.additionalProperties?.sf?.properties.inputMethodSurface
503        ?.screenBounds ?? undefined
504    );
505  }
506
507  sfImeContainerRect(): PropertyTreeNode | undefined {
508    return (
509      this.additionalProperties?.sf?.properties.inputMethodSurface?.rect ??
510      undefined
511    );
512  }
513
514  isAllPropertiesUndefined(): boolean {
515    if (this.isImeManagerService) {
516      return !this.additionalProperties?.wm;
517    } else {
518      return !(this.additionalProperties?.wm || this.additionalProperties?.sf);
519    }
520  }
521
522  onClickShowInPropertiesPanelWm(item: TreeNode, name: string) {
523    this.updateAdditionalPropertySelected(item, name);
524  }
525
526  onClickShowInPropertiesPanelSf(
527    item: ImeContainerProperties | InputMethodSurfaceProperties,
528  ) {
529    this.updateHighlightedItem(item.id);
530  }
531
532  private updateHighlightedItem(newId: string) {
533    const event: CustomEvent = new CustomEvent(
534      ViewerEvents.HighlightedIdChange,
535      {
536        bubbles: true,
537        detail: {id: newId},
538      },
539    );
540    this.elementRef.nativeElement.dispatchEvent(event);
541  }
542
543  private updateAdditionalPropertySelected(item: TreeNode, name: string) {
544    const itemWrapper = {
545      name,
546      treeNode: item,
547    };
548    const event: CustomEvent = new CustomEvent(
549      ViewerEvents.AdditionalPropertySelected,
550      {
551        bubbles: true,
552        detail: {selectedItem: itemWrapper},
553      },
554    );
555    this.elementRef.nativeElement.dispatchEvent(event);
556  }
557}
558