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