• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  ChangeDetectorRef,
18  Component,
19  EventEmitter,
20  Inject,
21  Input,
22  NgZone,
23  Output,
24} from '@angular/core';
25import {TracePipeline} from 'app/trace_pipeline';
26import {ProgressListener} from 'messaging/progress_listener';
27import {WinscopeEvent, WinscopeEventType} from 'messaging/winscope_event';
28import {WinscopeEventListener} from 'messaging/winscope_event_listener';
29import {Trace} from 'trace/trace';
30import {TRACE_INFO} from 'trace/trace_info';
31import {TraceTypeUtils} from 'trace/trace_type';
32import {LoadProgressComponent} from './load_progress_component';
33
34@Component({
35  selector: 'upload-traces',
36  template: `
37    <mat-card class="upload-card">
38      <div class="card-header">
39        <mat-card-title class="title">Upload Traces</mat-card-title>
40        <div
41          *ngIf="!isLoadingFiles && tracePipeline.getTraces().getSize() > 0"
42          class="trace-actions-container">
43          <button
44            color="primary"
45            mat-raised-button
46            class="load-btn"
47            matTooltip="Upload trace with an associated viewer to visualize"
48            [matTooltipDisabled]="hasLoadedFilesWithViewers()"
49            [disabled]="isViewTracesButtonDisabled()"
50            (click)="onViewTracesButtonClick()">
51            View traces
52          </button>
53
54          <button
55            class="download-btn"
56            color="primary"
57            mat-stroked-button
58            (click)="downloadTracesClick.emit()">Download all</button>
59
60          <button
61            class="upload-btn"
62            color="primary"
63            mat-stroked-button
64            for="fileDropRef"
65            [disabled]="viewersLoading"
66            (click)="fileDropRef.click()">
67            Upload another file
68          </button>
69
70          <button
71            class="clear-all-btn"
72            color="primary"
73            mat-stroked-button
74            [disabled]="viewersLoading"
75            (click)="onClearButtonClick()">
76            Clear all
77          </button>
78        </div>
79      </div>
80
81      <mat-card-content
82        class="drop-box"
83        ref="drop-box"
84        (dragleave)="onFileDragOut($event)"
85        (dragover)="onFileDragIn($event)"
86        (drop)="onFileDrop($event)"
87        (click)="fileDropRef.click()">
88        <input
89          id="fileDropRef"
90          hidden
91          type="file"
92          multiple
93          onclick="this.value = null"
94          #fileDropRef
95          (change)="onInputFiles($event)" />
96
97        <load-progress
98          *ngIf="isLoadingFiles"
99          [progressPercentage]="progressPercentage"
100          [message]="progressMessage">
101        </load-progress>
102
103        <mat-list
104          *ngIf="!isLoadingFiles && tracePipeline.getTraces().getSize() > 0"
105          class="uploaded-files">
106          <mat-list-item
107            [class.no-visualization]="!canVisualizeTrace(trace)"
108            [class.trace-error]="trace.isCorrupted()"
109            *ngFor="let trace of tracePipeline.getTraces()">
110            <mat-icon
111              matListIcon
112              [style]="{color: TRACE_INFO[trace.type].color}">
113              {{ TRACE_INFO[trace.type].icon }}
114            </mat-icon>
115
116            <p matLine>{{ TRACE_INFO[trace.type].name }}</p>
117            <p matLine *ngFor="let descriptor of trace.getDescriptors()">{{ descriptor }}</p>
118
119            <mat-icon
120              class="warning-icon"
121              *ngIf="!canVisualizeTrace(trace)"
122              [matTooltip]="cannotVisualizeTraceTooltip(trace)">warning</mat-icon>
123            <mat-icon
124              class="error-icon"
125              *ngIf="trace.isCorrupted()"
126              [matTooltip]="traceErrorTooltip(trace)">error</mat-icon>
127            <button
128              mat-icon-button
129              (click)="onRemoveTrace($event, trace)"
130              [disabled]="viewersLoading">
131              <mat-icon>close</mat-icon>
132            </button>
133          </mat-list-item>
134        </mat-list>
135
136        <div
137          *ngIf="!isLoadingFiles && tracePipeline.getTraces().getSize() === 0"
138          class="drop-info">
139          <p class="mat-body-3 icon">
140            <mat-icon inline fontIcon="upload"></mat-icon>
141          </p>
142          <p class="mat-body-1">Drag your .winscope file(s) or click to upload</p>
143        </div>
144      </mat-card-content>
145    </mat-card>
146  `,
147  styles: [
148    `
149      .upload-card {
150        height: 100%;
151        display: flex;
152        flex-direction: column;
153        overflow: auto;
154        margin: 10px;
155        padding-top: 0px;
156      }
157      .card-header {
158        justify-content: space-between;
159        align-items: center;
160        display: flex;
161        flex-direction: row;
162      }
163      .title {
164        padding-top: 16px;
165        text-align: center;
166      }
167      .trace-actions-container {
168        display: flex;
169        flex-direction: row;
170        flex-wrap: wrap;
171        gap: 10px;
172        padding: 4px 0px;
173      }
174      .drop-box {
175        display: flex;
176        flex-direction: column;
177        overflow: auto;
178        border: 2px dashed var(--border-color);
179        cursor: pointer;
180      }
181      .uploaded-files {
182        flex: 400px;
183        padding: 0;
184      }
185      .drop-info {
186        flex: 400px;
187        display: flex;
188        flex-direction: column;
189        justify-content: center;
190        align-items: center;
191        pointer-events: none;
192      }
193      .drop-info p {
194        opacity: 0.6;
195        font-size: 1.2rem;
196      }
197      .drop-info .icon {
198        font-size: 3rem;
199        margin: 0;
200      }
201      .div-progress {
202        display: flex;
203        height: 100%;
204        flex-direction: column;
205        justify-content: center;
206        align-content: center;
207        align-items: center;
208      }
209      .div-progress p {
210        opacity: 0.6;
211      }
212      .div-progress mat-icon {
213        font-size: 3rem;
214        width: unset;
215        height: unset;
216      }
217      .div-progress mat-progress-bar {
218        max-width: 250px;
219      }
220      mat-card-content {
221        flex-grow: 1;
222      }
223      .no-visualization {
224        background-color: var(--warning-background-color);
225      }
226      .trace-error {
227        background-color: var(--error-background-color);
228      }
229      .warning-icon, .error-icon {
230        flex-shrink: 0;
231      }
232    `,
233  ],
234})
235export class UploadTracesComponent
236  implements WinscopeEventListener, ProgressListener
237{
238  TRACE_INFO = TRACE_INFO;
239  isLoadingFiles = false;
240  progressMessage = '';
241  progressPercentage?: number;
242  lastUiProgressUpdateTimeMs?: number;
243  viewersLoading = false;
244
245  @Input() tracePipeline: TracePipeline | undefined;
246  @Output() filesUploaded = new EventEmitter<File[]>();
247  @Output() viewTracesButtonClick = new EventEmitter<void>();
248  @Output() downloadTracesClick = new EventEmitter<void>();
249
250  constructor(
251    @Inject(ChangeDetectorRef) private changeDetectorRef: ChangeDetectorRef,
252    @Inject(NgZone) private ngZone: NgZone,
253  ) {}
254
255  ngOnInit() {
256    this.tracePipeline?.clear();
257  }
258
259  async onWinscopeEvent(event: WinscopeEvent) {
260    await event.visit(WinscopeEventType.APP_TRACE_VIEW_REQUEST, async () => {
261      this.viewersLoading = true;
262    });
263    await event.visit(
264      WinscopeEventType.APP_TRACE_VIEW_REQUEST_HANDLED,
265      async () => {
266        this.viewersLoading = false;
267      },
268    );
269  }
270
271  onProgressUpdate(
272    message: string | undefined,
273    progressPercentage: number | undefined,
274  ) {
275    if (
276      !LoadProgressComponent.canUpdateComponent(this.lastUiProgressUpdateTimeMs)
277    ) {
278      return;
279    }
280    this.isLoadingFiles = true;
281    this.progressMessage = message ? message : 'Loading...';
282    this.progressPercentage = progressPercentage;
283    this.lastUiProgressUpdateTimeMs = Date.now();
284    this.changeDetectorRef.detectChanges();
285  }
286
287  onOperationFinished() {
288    this.isLoadingFiles = false;
289    this.lastUiProgressUpdateTimeMs = undefined;
290    this.changeDetectorRef.detectChanges();
291  }
292
293  onInputFiles(event: Event) {
294    if (this.viewersLoading) {
295      return;
296    }
297    const files = this.getInputFiles(event);
298    if (files.length === 0) return;
299    this.filesUploaded.emit(files);
300  }
301
302  onViewTracesButtonClick() {
303    this.viewTracesButtonClick.emit();
304  }
305
306  onClearButtonClick() {
307    this.tracePipeline?.clear();
308    this.onOperationFinished();
309  }
310
311  onFileDragIn(e: DragEvent) {
312    e.preventDefault();
313    e.stopPropagation();
314  }
315
316  onFileDragOut(e: DragEvent) {
317    e.preventDefault();
318    e.stopPropagation();
319  }
320
321  onFileDrop(e: DragEvent) {
322    if (this.viewersLoading) {
323      return;
324    }
325    e.preventDefault();
326    e.stopPropagation();
327    const droppedFiles = e.dataTransfer?.files;
328    if (!droppedFiles) return;
329    this.filesUploaded.emit(Array.from(droppedFiles));
330  }
331
332  onRemoveTrace(event: MouseEvent, trace: Trace<object>) {
333    event.preventDefault();
334    event.stopPropagation();
335    this.tracePipeline?.removeTrace(trace);
336    this.onOperationFinished();
337  }
338
339  hasLoadedFilesWithViewers(): boolean {
340    return this.ngZone.run(() => {
341      let hasFilesWithViewers = false;
342      this.tracePipeline?.getTraces().forEachTrace((trace) => {
343        if (
344          !trace.isCorrupted() &&
345          TraceTypeUtils.isTraceTypeWithViewer(trace.type)
346        ) {
347          hasFilesWithViewers = true;
348        }
349      });
350
351      return hasFilesWithViewers;
352    });
353  }
354
355  isViewTracesButtonDisabled(): boolean {
356    return this.viewersLoading || !this.hasLoadedFilesWithViewers();
357  }
358
359  canVisualizeTrace(trace: Trace<object>): boolean {
360    return TraceTypeUtils.isTraceTypeWithViewer(trace.type);
361  }
362
363  cannotVisualizeTraceTooltip(trace: Trace<object>): string {
364    return TraceTypeUtils.getReasonForNoTraceVisualization(trace.type);
365  }
366
367  traceErrorTooltip(trace: Trace<object>): string {
368    const reason = trace.getCorruptedReason() ?? 'Trace is corrupted.';
369    return 'Cannot visualize trace. ' + reason;
370  }
371
372  private getInputFiles(event: Event): File[] {
373    const files: FileList | null = (event?.target as HTMLInputElement)?.files;
374    if (!files || !files[0]) {
375      return [];
376    }
377    return Array.from(files);
378  }
379}
380