Add tags and errors to focused/targeted timelines.

Extend the tag timeline display from global to specific WM/SF trace
timelines. During focused or targeted navigation, the tags/errors should display for their associated trace
only. On global navigation, the minimum start and maximum end timestamp
for each transition is displayed. In the search bar, under transitions
and errors, note that the timestamps displayed as the global start and
end.

Bug: b/197066003

Test: upload SampleWinscope.zip from bug and test the different
navigation types.

Change-Id: Id273bf77425c504d943f1ee9716ce2f3ad0ec9c5
diff --git a/tools/winscope/src/Overlay.vue b/tools/winscope/src/Overlay.vue
index 35fbfe7..d098dd4 100644
--- a/tools/winscope/src/Overlay.vue
+++ b/tools/winscope/src/Overlay.vue
@@ -112,12 +112,6 @@
                       desc="Consider all timelines for navigation"
                     />
                     <md-icon-option
-                      v-if="tagAndErrorTraces"
-                      :value="NAVIGATION_STYLE.FLICKER"
-                      icon="details"
-                      desc="Display transition tags and flicker errors on global timeline"
-                    />
-                    <md-icon-option
                       :value="NAVIGATION_STYLE.FOCUSED"
                       :icon="TRACE_ICONS[focusedFile.type]"
                       :desc="`Automatically switch what timeline is considered
@@ -161,8 +155,8 @@
                 <timeline
                   :store="store"
                   :flickerMode="flickerMode"
-                  :tags="Object.freeze(presentTags)"
-                  :errors="Object.freeze(presentErrors)"
+                  :tags="Object.freeze(tags)"
+                  :errors="Object.freeze(errors)"
                   :timeline="Object.freeze(minimizedTimeline.timeline)"
                   :selected-index="minimizedTimeline.selectedIndex"
                   :scale="scale"
@@ -289,7 +283,7 @@
 import Searchbar from './Searchbar.vue';
 import FileType from './mixins/FileType.js';
 import {NAVIGATION_STYLE} from './utils/consts';
-import {TRACE_ICONS} from '@/decode.js';
+import {TRACE_ICONS, FILE_TYPES} from '@/decode.js';
 
 // eslint-disable-next-line camelcase
 import {nanos_to_string} from './transform.js';
@@ -318,6 +312,8 @@
       cropIntent: null,
       TRACE_ICONS,
       search: false,
+      tags: [],
+      errors: [],
     };
   },
   created() {
@@ -336,8 +332,7 @@
       // Only store navigation type in local store if it's a type that will
       // work regardless of what data is loaded.
       if (style === NAVIGATION_STYLE.GLOBAL ||
-        style === NAVIGATION_STYLE.FOCUSED ||
-        style === NAVIGATION_STYLE.FLICKER) {
+        style === NAVIGATION_STYLE.FOCUSED) {
         this.store.navigationStyle = style;
       }
       this.updateNavigationFileFilter();
@@ -396,9 +391,6 @@
         case NAVIGATION_STYLE.GLOBAL:
           return 'All timelines';
 
-        case NAVIGATION_STYLE.FLICKER:
-          return 'All timelines with tags and errors';
-
         case NAVIGATION_STYLE.FOCUSED:
           return `Focused: ${this.focusedFile.type}`;
 
@@ -422,9 +414,6 @@
         case NAVIGATION_STYLE.GLOBAL:
           return 'public';
 
-        case NAVIGATION_STYLE.FLICKER:
-          return 'details';
-
         case NAVIGATION_STYLE.FOCUSED:
           return TRACE_ICONS[this.focusedFile.type];
 
@@ -444,11 +433,9 @@
       }
     },
     minimizedTimeline() {
-      if (this.navigationStyle === NAVIGATION_STYLE.GLOBAL) {
-        return this.mergedTimeline;
-      }
+      this.updateFlickerMode(this.navigationStyle);
 
-      if (this.navigationStyle === NAVIGATION_STYLE.FLICKER) {
+      if (this.navigationStyle === NAVIGATION_STYLE.GLOBAL) {
         return this.mergedTimeline;
       }
 
@@ -465,7 +452,10 @@
         return this.mergedTimeline;
       }
 
-      if (this.navigationStyle.split('-')[0] === NAVIGATION_STYLE.TARGETED) {
+      if (
+        this.navigationStyle.split('-').length >= 2
+        && this.navigationStyle.split('-')[0] === NAVIGATION_STYLE.TARGETED
+      ) {
         return this.$store.state
             .traces[this.navigationStyle.split('-')[1]];
       }
@@ -481,7 +471,7 @@
       return this.timelineFiles.length > 1;
     },
     flickerMode() {
-      return this.navigationStyle === NAVIGATION_STYLE.FLICKER;
+      return this.tags.length>0 || this.errors.length>0;
     },
   },
   updated() {
@@ -633,10 +623,6 @@
           navigationStyleFilter = (f) => true;
           break;
 
-        case NAVIGATION_STYLE.FLICKER:
-          navigationStyleFilter = (f) => true;
-          break;
-
         case NAVIGATION_STYLE.FOCUSED:
           navigationStyleFilter =
             (f) => f.type === this.focusedFile.type;
@@ -661,6 +647,43 @@
 
       this.$store.commit('setNavigationFilesFilter', navigationStyleFilter);
     },
+    updateFlickerMode(style) {
+      if (style === NAVIGATION_STYLE.GLOBAL ||
+        style === NAVIGATION_STYLE.CUSTOM) {
+        this.tags = this.presentTags;
+        this.errors = this.presentErrors;
+
+      } else if (style === NAVIGATION_STYLE.FOCUSED) {
+        if (this.focusedFile.timeline) {
+          this.tags = this.getTagTimelineComponents(this.presentTags, this.focusedFile);
+          this.errors = this.getTagTimelineComponents(this.presentErrors, this.focusedFile);
+        }
+      } else if (
+        style.split('-').length >= 2 &&
+        style.split('-')[0] === NAVIGATION_STYLE.TARGETED
+      ) {
+        const file = this.$store.state.traces[style.split('-')[1]];
+        if (file.timeline) {
+          this.tags = this.getTagTimelineComponents(this.presentTags, file);
+          this.errors = this.getTagTimelineComponents(this.presentErrors, file);
+        }
+      //Unexpected navigation type or no timeline present in file
+      } else {
+        console.warn('Unexpected timeline or navigation type; no flicker mode available');
+        this.tags = [];
+        this.errors = [];
+      }
+    },
+    getTagTimelineComponents(items, file) {
+      if (file.type===FILE_TYPES.SURFACE_FLINGER_TRACE) {
+        return items.filter(item => item.layerId !== -1);
+      }
+      if (file.type===FILE_TYPES.WINDOW_MANAGER_TRACE) {
+        return items.filter(item => item.taskId !== -1);
+      }
+      // if focused file is not one supported by tags/errors
+      return [];
+    },
     updateVideoOverlayWidth(width) {
       this.videoOverlayExtraWidth = width;
     },
diff --git a/tools/winscope/src/Searchbar.vue b/tools/winscope/src/Searchbar.vue
index 9178a58..1f5fa4e 100644
--- a/tools/winscope/src/Searchbar.vue
+++ b/tools/winscope/src/Searchbar.vue
@@ -30,8 +30,8 @@
     <div class="dropdown-content" v-if="isTagSearch()">
       <table>
         <tr class="header">
-          <th style="width: 10%">Start</th>
-          <th style="width: 10%">End</th>
+          <th style="width: 10%">Global Start</th>
+          <th style="width: 10%">Global End</th>
           <th style="width: 80%">Description</th>
         </tr>
 
diff --git a/tools/winscope/src/mixins/Timeline.js b/tools/winscope/src/mixins/Timeline.js
index 8bd223e..ec9cde9 100644
--- a/tools/winscope/src/mixins/Timeline.js
+++ b/tools/winscope/src/mixins/Timeline.js
@@ -149,9 +149,13 @@
 
       for (const transitionId in groupedTags) {
         const id = groupedTags[transitionId];
-        //there are two tags per id; check which tag is the start, which is end
-        const transitionStartTime = (id[0].isStartTag) ? id[0].timestamp : id[1].timestamp;
-        const transitionEndTime = (!id[0].isStartTag) ? id[0].timestamp : id[1].timestamp;
+        //there are at least two tags per id, maybe more if multiple traces
+        // determine which tag is the start (min of start times), which is end (max of end times)
+        const startTimes = id.filter(tag => tag.isStartTag).map(tag => tag.timestamp);
+        const endTimes = id.filter(tag => !tag.isStartTag).map(tag => tag.timestamp);
+
+        const transitionStartTime = Math.min(startTimes);
+        const transitionEndTime = Math.max(endTimes);
 
         //do not freeze new transition, as overlap still to be handled (defaulted to 0)
         const transition = this.generateTransition(
diff --git a/tools/winscope/src/utils/consts.js b/tools/winscope/src/utils/consts.js
index 684c1c2..e43a2c1 100644
--- a/tools/winscope/src/utils/consts.js
+++ b/tools/winscope/src/utils/consts.js
@@ -31,7 +31,6 @@
   FOCUSED: 'Focused',
   CUSTOM: 'Custom',
   TARGETED: 'Targeted',
-  FLICKER: 'Flicker',
 };
 
 const SEARCH_TYPE = {