Merge "ui: fix recording on P"
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index c7053d1..028fde9 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -315,6 +315,10 @@
 // 'Q','P','O' for Android, 'L' for Linux, 'C' for Chrome.
 export declare type TargetOs = 'Q' | 'P' | 'O' | 'C' | 'L';
 
+export function isAndroidP(target: RecordingTarget) {
+  return target.os === 'P';
+}
+
 export function isAndroidTarget(target: RecordingTarget) {
   return ['Q', 'P', 'O'].includes(target.os);
 }
diff --git a/ui/src/controller/record_controller.ts b/ui/src/controller/record_controller.ts
index 1913817..5b70619 100644
--- a/ui/src/controller/record_controller.ts
+++ b/ui/src/controller/record_controller.ts
@@ -39,6 +39,7 @@
 import {
   AdbRecordingTarget,
   isAdbTarget,
+  isAndroidP,
   isChromeTarget,
   MAX_TIME,
   RecordConfig,
@@ -62,11 +63,13 @@
 
 type RPCImplMethod = (Method|rpc.ServiceMethod<Message<{}>, Message<{}>>);
 
-export function genConfigProto(uiCfg: RecordConfig): Uint8Array {
-  return TraceConfig.encode(genConfig(uiCfg)).finish();
+export function genConfigProto(
+    uiCfg: RecordConfig, target: RecordingTarget): Uint8Array {
+  return TraceConfig.encode(genConfig(uiCfg, target)).finish();
 }
 
-export function genConfig(uiCfg: RecordConfig): TraceConfig {
+export function genConfig(
+    uiCfg: RecordConfig, target: RecordingTarget): TraceConfig {
   const protoCfg = new TraceConfig();
   protoCfg.durationMs = uiCfg.durationMs;
 
@@ -441,7 +444,28 @@
       ftraceEvents.add('ftrace/print');
     }
 
-    ds.config.ftraceConfig.ftraceEvents = Array.from(ftraceEvents);
+    var ftraceEventsArray = Array<string>();
+    if (isAndroidP(target)) {
+      for (const ftraceEvent of ftraceEvents) {
+        // On P, we don't support groups so strip all group names from ftrace
+        // events.
+        const groupAndName = ftraceEvent.split('/');
+        if (groupAndName.length != 2) {
+          ftraceEventsArray.push(ftraceEvent);
+          continue;
+        }
+        // Filter out any wildcard event groups which was not supported
+        // before Q.
+        if (groupAndName[1] === '*') {
+          continue;
+        }
+        ftraceEventsArray.push(groupAndName[1]);
+      }
+    } else {
+      ftraceEventsArray = Array.from(ftraceEvents)
+    }
+
+    ds.config.ftraceConfig.ftraceEvents = ftraceEventsArray;
     ds.config.ftraceConfig.atraceCategories = Array.from(atraceCats);
     ds.config.ftraceConfig.atraceApps = Array.from(atraceApps);
     protoCfg.dataSources.push(ds);
@@ -536,19 +560,26 @@
     }
     this.config = this.app.state.recordConfig;
 
-    const configProto = genConfigProto(this.config);
+    const configProto =
+        genConfigProto(this.config, this.app.state.recordingTarget);
     const configProtoText = toPbtxt(configProto);
+    const configProtoBase64 = uint8ArrayToBase64(configProto);
     const commandline = `
-      echo '${uint8ArrayToBase64(configProto)}' |
+      echo '${configProtoBase64}' |
       base64 --decode |
       adb shell "perfetto -c - -o /data/misc/perfetto-traces/trace" &&
       adb pull /data/misc/perfetto-traces/trace /tmp/trace
     `;
-    const traceConfig = genConfig(this.config);
+    const traceConfig = genConfig(this.config, this.app.state.recordingTarget);
     // TODO(hjd): This should not be TrackData after we unify the stores.
     this.app.publish('TrackData', {
       id: 'config',
-      data: {commandline, pbtxt: configProtoText, traceConfig}
+      data: {
+        commandline,
+        pbBase64: configProtoBase64,
+        pbtxt: configProtoText,
+        traceConfig
+      }
     });
 
     // If the recordingInProgress boolean state is different, it means that we
diff --git a/ui/src/controller/record_controller_jsdomtest.ts b/ui/src/controller/record_controller_jsdomtest.ts
index 77da1ff..07f1487 100644
--- a/ui/src/controller/record_controller_jsdomtest.ts
+++ b/ui/src/controller/record_controller_jsdomtest.ts
@@ -24,14 +24,16 @@
 test('encodeConfig', () => {
   const config = createEmptyRecordConfig();
   config.durationSeconds = 10;
-  const result = TraceConfig.decode(genConfigProto(config));
+  const result =
+      TraceConfig.decode(genConfigProto(config, {os: 'Q', name: 'Android Q'}));
   expect(result.durationMs).toBe(10000);
 });
 
 test('SysConfig', () => {
   const config = createEmptyRecordConfig();
   config.cpuSyscall = true;
-  const result = TraceConfig.decode(genConfigProto(config));
+  const result =
+      TraceConfig.decode(genConfigProto(config, {os: 'Q', name: 'Android Q'}));
   const sources = assertExists(result.dataSources);
   const srcConfig = assertExists(sources[0].config);
   const ftraceConfig = assertExists(srcConfig.ftraceConfig);
@@ -108,7 +110,8 @@
   config.ipcFlows = true;
   config.jsExecution = true;
   config.mode = 'STOP_WHEN_FULL';
-  const result = TraceConfig.decode(genConfigProto(config));
+  const result =
+      TraceConfig.decode(genConfigProto(config, {os: 'C', name: 'Chrome'}));
   const sources = assertExists(result.dataSources);
 
   const traceConfigSource = assertExists(sources[0].config);
@@ -133,7 +136,8 @@
   config.ipcFlows = true;
   config.jsExecution = true;
   config.mode = 'RING_BUFFER';
-  const result = TraceConfig.decode(genConfigProto(config));
+  const result =
+      TraceConfig.decode(genConfigProto(config, {os: 'C', name: 'Chrome'}));
   const sources = assertExists(result.dataSources);
 
   const traceConfigSource = assertExists(sources[0].config);
@@ -159,7 +163,8 @@
   config.ipcFlows = true;
   config.jsExecution = true;
   config.mode = 'RING_BUFFER';
-  const result = TraceConfig.decode(genConfigProto(config));
+  const result =
+      TraceConfig.decode(genConfigProto(config, {os: 'C', name: 'Chrome'}));
   const sources = assertExists(result.dataSources);
 
   const traceConfigSource = assertExists(sources[0].config);
diff --git a/ui/src/frontend/record_page.ts b/ui/src/frontend/record_page.ts
index 3890e70..a1a4440 100644
--- a/ui/src/frontend/record_page.ts
+++ b/ui/src/frontend/record_page.ts
@@ -23,6 +23,7 @@
   getBuiltinChromeCategoryList,
   getDefaultRecordingTargets,
   isAdbTarget,
+  isAndroidP,
   isAndroidTarget,
   isChromeTarget,
   RecordingTarget
@@ -140,7 +141,7 @@
       m('.record-mode',
         recButton('STOP_WHEN_FULL', 'Stop when full', 'rec_one_shot.png'),
         recButton('RING_BUFFER', 'Ring buffer', 'rec_ring_buf.png'),
-        recButton('LONG_TRACE', 'Long trace', 'rec_long_trace.png'), ),
+        recButton('LONG_TRACE', 'Long trace', 'rec_long_trace.png')),
 
       m(Slider, {
         title: 'In-memory buffer size',
@@ -210,15 +211,13 @@
 }
 
 function GpuSettings(cssClass: string) {
-  return m(
-      `.record-section${cssClass}`,
-      m(Probe, {
-        title: 'GPU frequency',
-        img: 'rec_cpu_freq.png',
-        descr: 'Records gpu frequency via ftrace',
-        setEnabled: (cfg, val) => cfg.gpuFreq = val,
-        isEnabled: (cfg) => cfg.gpuFreq
-      } as ProbeAttrs));
+  return m(`.record-section${cssClass}`, m(Probe, {
+             title: 'GPU frequency',
+             img: 'rec_cpu_freq.png',
+             descr: 'Records gpu frequency via ftrace',
+             setEnabled: (cfg, val) => cfg.gpuFreq = val,
+             isEnabled: (cfg) => cfg.gpuFreq
+           } as ProbeAttrs));
 }
 
 function CpuSettings(cssClass: string) {
@@ -741,18 +740,19 @@
               img: null,
               descr: `Records the screen along with running a trace. Max
                   time of recording is 3 minutes (180 seconds).`,
-          setEnabled: (cfg, val) => cfg.screenRecord = val,
-          isEnabled: (cfg) => cfg.screenRecord,
-        } as ProbeAttrs,
-        m(Slider, {
-          title: 'Max duration',
-          icon: 'timer',
-          values: [S(10), S(15), S(30), S(60), M(2), M(3)],
-          isTime: true,
-          unit: 'm:s',
-          set: (cfg, val) => cfg.durationMs = val,
-          get: (cfg) => cfg.durationMs,
-        } as SliderAttrs),) : null);
+              setEnabled: (cfg, val) => cfg.screenRecord = val,
+              isEnabled: (cfg) => cfg.screenRecord,
+            } as ProbeAttrs,
+            m(Slider, {
+              title: 'Max duration',
+              icon: 'timer',
+              values: [S(10), S(15), S(30), S(60), M(2), M(3)],
+              isTime: true,
+              unit: 'm:s',
+              set: (cfg, val) => cfg.durationMs = val,
+              get: (cfg) => cfg.durationMs,
+            } as SliderAttrs)) :
+          null);
 }
 
 function RecordHeader() {
@@ -863,6 +863,11 @@
   const msgPerfettoNotSupported =
       m('div', `Perfetto is not supported natively before Android P.`);
 
+  const msgRecordingNotSupported =
+      m('div', `Recording Perfetto traces from the UI is not supported natively
+     before Android Q. If you are using a P device, please select 'Android P'
+     as the 'Target Platform' and collect the trace using ADB`);
+
   const msgSideload =
       m('div',
         `If you have a rooted device you can sideload the latest version of
@@ -882,6 +887,9 @@
       output directory. `,
         doc);
 
+  if (isAdbTarget(globals.state.recordingTarget)) {
+    notes.push(msgRecordingNotSupported);
+  }
   switch (globals.state.recordingTarget.os) {
     case 'Q':
       break;
@@ -922,7 +930,7 @@
 
 function getRecordCommand(target: RecordingTarget) {
   const data = globals.trackDataStore.get('config') as
-          {commandline: string, pbtxt: string} |
+          {commandline: string, pbtxt: string, pbBase64: string} |
       null;
 
   const cfg = globals.state.recordConfig;
@@ -932,6 +940,7 @@
     time = MAX_TIME;
   }
 
+  const pbBase64 = data ? data.pbBase64 : '';
   const pbtx = data ? data.pbtxt : '';
   let cmd = '';
   if (cfg.screenRecord) {
@@ -940,13 +949,19 @@
     cmd += `(sleep 0.5 && adb shell screenrecord --time-limit ${time}`;
     cmd += ' "/sdcard/tracescr.mp4") &\\\n';
   }
-  cmd += isAndroidTarget(target) ? 'adb shell perfetto \\\n' : 'perfetto \\\n';
-  cmd += '  -c - --txt \\\n';
-  cmd += '  -o /data/misc/perfetto-traces/trace \\\n';
-  cmd += '<<EOF\n\n';
-  cmd += pbtx;
-  cmd += '\nEOF\n';
-
+  if (isAndroidP(target)) {
+    cmd += `echo '${pbBase64}' | \n`;
+    cmd += 'base64 --decode | \n';
+    cmd += 'adb shell "perfetto -c - -o /data/misc/perfetto-traces/trace"\n';
+  } else {
+    cmd +=
+        isAndroidTarget(target) ? 'adb shell perfetto \\\n' : 'perfetto \\\n';
+    cmd += '  -c - --txt \\\n';
+    cmd += '  -o /data/misc/perfetto-traces/trace \\\n';
+    cmd += '<<EOF\n\n';
+    cmd += pbtx;
+    cmd += '\nEOF\n';
+  }
   return cmd;
 }