Track per-window information in ViewFrameInfo
FrameInfo will now be per-window; that is, per-ViewRootImpl.
Some of the information should remain “global” (it remain in Choreographer),
while some information is going to become ViewRootImpl-specific.
Before the information gets passed to the native layer,
the ViewRootImpl-specific info will be stitched together
with the general Choreographer info.
This change is useful in order to correctly correlate frames with a specific
input event. In the unlikely scenario of a user touching two windows of the
same app simultaneously, this change will allow us to correctly measure the
latency of both frames produced by the windows.
Design doc: https://docs.google.com/document/d/1KMpMBlOxnl7zkWBCbXZZE6ZlaHEA4efYnN6WYK8n3FE/edit?resourcekey=0-eqooVNP0SskupljlTFvtOQ
Test: atest ViewFrameInfoTest
Bug: 169866723
Change-Id: Ib0bf9cd51cbcc0b9b70460c929c480eb490ec322
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 57ca71a..d839e35 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -21,6 +21,7 @@
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.TypedArray;
+import android.graphics.FrameInfo;
import android.graphics.HardwareRenderer;
import android.graphics.Picture;
import android.graphics.Point;
@@ -610,8 +611,7 @@
* @param attachInfo AttachInfo tied to the specified view.
*/
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
- final Choreographer choreographer = attachInfo.mViewRootImpl.mChoreographer;
- choreographer.mFrameInfo.markDrawStart();
+ attachInfo.mViewRootImpl.mViewFrameInfo.markDrawStart();
updateRootDisplayList(view, callbacks);
@@ -629,7 +629,9 @@
attachInfo.mPendingAnimatingRenderNodes = null;
}
- int syncResult = syncAndDrawFrame(choreographer.mFrameInfo);
+ final FrameInfo frameInfo = attachInfo.mViewRootImpl.getUpdatedFrameInfo();
+
+ int syncResult = syncAndDrawFrame(frameInfo);
if ((syncResult & SYNC_LOST_SURFACE_REWARD_IF_FOUND) != 0) {
Log.w("OpenGLRenderer", "Surface lost, forcing relayout");
// We lost our surface. For a relayout next frame which should give us a new
diff --git a/core/java/android/view/ViewFrameInfo.java b/core/java/android/view/ViewFrameInfo.java
new file mode 100644
index 0000000..890d071
--- /dev/null
+++ b/core/java/android/view/ViewFrameInfo.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.graphics.FrameInfo;
+
+/**
+ * The timing information of events taking place in ViewRootImpl
+ * @hide
+ */
+public class ViewFrameInfo {
+ public long drawStart;
+ public long oldestInputEventTime; // the time of the oldest input event consumed for this frame
+ public long newestInputEventTime; // the time of the newest input event consumed for this frame
+ // Various flags set to provide extra metadata about the current frame. See flag definitions
+ // inside FrameInfo.
+ // @see android.graphics.FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED
+ public long flags;
+
+ /**
+ * Update the oldest event time.
+ * @param eventTime the time of the input event
+ */
+ public void updateOldestInputEvent(long eventTime) {
+ if (oldestInputEventTime == 0 || eventTime < oldestInputEventTime) {
+ oldestInputEventTime = eventTime;
+ }
+ }
+
+ /**
+ * Update the newest event time.
+ * @param eventTime the time of the input event
+ */
+ public void updateNewestInputEvent(long eventTime) {
+ if (newestInputEventTime == 0 || eventTime > newestInputEventTime) {
+ newestInputEventTime = eventTime;
+ }
+ }
+
+ /**
+ * Populate the missing fields using the data from ViewFrameInfo
+ * @param frameInfo : the structure FrameInfo object to populate
+ */
+ public void populateFrameInfo(FrameInfo frameInfo) {
+ frameInfo.frameInfo[FrameInfo.FLAGS] |= flags;
+ frameInfo.frameInfo[FrameInfo.DRAW_START] = drawStart;
+ frameInfo.frameInfo[FrameInfo.OLDEST_INPUT_EVENT] = oldestInputEventTime;
+ frameInfo.frameInfo[FrameInfo.NEWEST_INPUT_EVENT] = newestInputEventTime;
+ }
+
+ /**
+ * Reset this data. Should typically be invoked after calling "populateFrameInfo".
+ */
+ public void reset() {
+ drawStart = 0;
+ oldestInputEventTime = 0;
+ newestInputEventTime = 0;
+ flags = 0;
+ }
+
+ /**
+ * Record the current time, and store it in 'drawStart'
+ */
+ public void markDrawStart() {
+ drawStart = System.nanoTime();
+ }
+}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2bea0d6..ae162ed 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -423,7 +423,7 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
int mHeight;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- Rect mDirty;
+ private Rect mDirty;
public boolean mIsAnimating;
private boolean mUseMTRenderer;
@@ -446,6 +446,23 @@
@UnsupportedAppUsage
FallbackEventHandler mFallbackEventHandler;
final Choreographer mChoreographer;
+ protected final ViewFrameInfo mViewFrameInfo = new ViewFrameInfo();
+
+ /**
+ * Update the Choreographer's FrameInfo object with the timing information for the current
+ * ViewRootImpl instance. Erase the data in the current ViewFrameInfo to prepare for the next
+ * frame.
+ * @return the updated FrameInfo object
+ */
+ protected @NonNull FrameInfo getUpdatedFrameInfo() {
+ // Since Choreographer is a thread-local singleton while we can have multiple
+ // ViewRootImpl's, populate the frame information from the current viewRootImpl before
+ // starting the draw
+ FrameInfo frameInfo = mChoreographer.mFrameInfo;
+ mViewFrameInfo.populateFrameInfo(frameInfo);
+ mViewFrameInfo.reset();
+ return frameInfo;
+ }
// used in relayout to get SurfaceControl size
// for BLAST adapter surface setup
@@ -2675,7 +2692,7 @@
// to resume them
mDirty.set(0, 0, mWidth, mHeight);
}
- mChoreographer.mFrameInfo.addFlags(FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED);
+ mViewFrameInfo.flags |= FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED;
}
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
@@ -8138,7 +8155,8 @@
oldestEventTime = me.getHistoricalEventTimeNano(0);
}
}
- mChoreographer.mFrameInfo.updateInputEventTime(eventTime, oldestEventTime);
+ mViewFrameInfo.updateOldestInputEvent(oldestEventTime);
+ mViewFrameInfo.updateNewestInputEvent(eventTime);
deliverInputEvent(q);
}
diff --git a/graphics/java/android/graphics/FrameInfo.java b/graphics/java/android/graphics/FrameInfo.java
index 0061ea1..d59abb5 100644
--- a/graphics/java/android/graphics/FrameInfo.java
+++ b/graphics/java/android/graphics/FrameInfo.java
@@ -43,7 +43,7 @@
public long[] frameInfo = new long[FRAME_INFO_SIZE];
// Various flags set to provide extra metadata about the current frame
- private static final int FLAGS = 0;
+ public static final int FLAGS = 0;
// Is this the first-draw following a window layout?
public static final long FLAG_WINDOW_LAYOUT_CHANGED = 1;
@@ -60,35 +60,35 @@
@Retention(RetentionPolicy.SOURCE)
public @interface FrameInfoFlags {}
- private static final int FRAME_TIMELINE_VSYNC_ID = 1;
+ public static final int FRAME_TIMELINE_VSYNC_ID = 1;
// The intended vsync time, unadjusted by jitter
- private static final int INTENDED_VSYNC = 2;
+ public static final int INTENDED_VSYNC = 2;
// Jitter-adjusted vsync time, this is what was used as input into the
// animation & drawing system
- private static final int VSYNC = 3;
+ public static final int VSYNC = 3;
// The time of the oldest input event
- private static final int OLDEST_INPUT_EVENT = 4;
+ public static final int OLDEST_INPUT_EVENT = 4;
// The time of the newest input event
- private static final int NEWEST_INPUT_EVENT = 5;
+ public static final int NEWEST_INPUT_EVENT = 5;
// When input event handling started
- private static final int HANDLE_INPUT_START = 6;
+ public static final int HANDLE_INPUT_START = 6;
// When animation evaluations started
- private static final int ANIMATION_START = 7;
+ public static final int ANIMATION_START = 7;
// When ViewRootImpl#performTraversals() started
- private static final int PERFORM_TRAVERSALS_START = 8;
+ public static final int PERFORM_TRAVERSALS_START = 8;
// When View:draw() started
- private static final int DRAW_START = 9;
+ public static final int DRAW_START = 9;
// When the frame needs to be ready by
- private static final int FRAME_DEADLINE = 10;
+ public static final int FRAME_DEADLINE = 10;
// Must be the last one
private static final int FRAME_INFO_SIZE = FRAME_DEADLINE + 1;
@@ -99,23 +99,11 @@
frameInfo[FRAME_TIMELINE_VSYNC_ID] = frameTimelineVsyncId;
frameInfo[INTENDED_VSYNC] = intendedVsync;
frameInfo[VSYNC] = usedVsync;
- frameInfo[OLDEST_INPUT_EVENT] = Long.MAX_VALUE;
- frameInfo[NEWEST_INPUT_EVENT] = 0;
frameInfo[FLAGS] = 0;
frameInfo[FRAME_DEADLINE] = frameDeadline;
}
/** checkstyle */
- public void updateInputEventTime(long inputEventTime, long inputEventOldestTime) {
- if (inputEventOldestTime < frameInfo[OLDEST_INPUT_EVENT]) {
- frameInfo[OLDEST_INPUT_EVENT] = inputEventOldestTime;
- }
- if (inputEventTime > frameInfo[NEWEST_INPUT_EVENT]) {
- frameInfo[NEWEST_INPUT_EVENT] = inputEventTime;
- }
- }
-
- /** checkstyle */
public void markInputHandlingStart() {
frameInfo[HANDLE_INPUT_START] = System.nanoTime();
}
@@ -131,13 +119,7 @@
}
/** checkstyle */
- public void markDrawStart() {
- frameInfo[DRAW_START] = System.nanoTime();
- }
-
- /** checkstyle */
public void addFlags(@FrameInfoFlags long flags) {
frameInfo[FLAGS] |= flags;
}
-
}
diff --git a/libs/hwui/FrameInfo.cpp b/libs/hwui/FrameInfo.cpp
index fd18d2f..8b20492 100644
--- a/libs/hwui/FrameInfo.cpp
+++ b/libs/hwui/FrameInfo.cpp
@@ -20,7 +20,7 @@
namespace android {
namespace uirenderer {
-const std::string FrameInfoNames[] = {
+const std::array<std::string, static_cast<int>(FrameInfoIndex::NumIndexes)> FrameInfoNames = {
"Flags",
"FrameTimelineVsyncId",
"IntendedVsync",
@@ -42,10 +42,6 @@
"GpuCompleted",
};
-static_assert((sizeof(FrameInfoNames) / sizeof(FrameInfoNames[0])) ==
- static_cast<int>(FrameInfoIndex::NumIndexes),
- "size mismatch: FrameInfoNames doesn't match the enum!");
-
static_assert(static_cast<int>(FrameInfoIndex::NumIndexes) == 19,
"Must update value in FrameMetrics.java#FRAME_STATS_COUNT (and here)");
diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h
index bb875e3..738246d 100644
--- a/libs/hwui/FrameInfo.h
+++ b/libs/hwui/FrameInfo.h
@@ -21,6 +21,7 @@
#include <cutils/compiler.h>
#include <utils/Timers.h>
+#include <array>
#include <memory.h>
#include <string>
@@ -60,7 +61,7 @@
NumIndexes
};
-extern const std::string FrameInfoNames[];
+extern const std::array<std::string, static_cast<int>(FrameInfoIndex::NumIndexes)> FrameInfoNames;
namespace FrameInfoFlags {
enum {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 9e0fb56..13450be 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -238,16 +238,16 @@
/* --- InputReaderPolicyInterface implementation --- */
- virtual void getReaderConfiguration(InputReaderConfiguration* outConfig);
- virtual std::shared_ptr<PointerControllerInterface> obtainPointerController(int32_t deviceId);
- virtual void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices);
- virtual std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
- const InputDeviceIdentifier& identifier);
- virtual std::string getDeviceAlias(const InputDeviceIdentifier& identifier);
- virtual TouchAffineTransformation getTouchAffineTransformation(JNIEnv *env,
- jfloatArray matrixArr);
- virtual TouchAffineTransformation getTouchAffineTransformation(
- const std::string& inputDeviceDescriptor, int32_t surfaceRotation);
+ void getReaderConfiguration(InputReaderConfiguration* outConfig) override;
+ std::shared_ptr<PointerControllerInterface> obtainPointerController(int32_t deviceId) override;
+ void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override;
+ std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
+ const InputDeviceIdentifier& identifier) override;
+ std::string getDeviceAlias(const InputDeviceIdentifier& identifier) override;
+ TouchAffineTransformation getTouchAffineTransformation(const std::string& inputDeviceDescriptor,
+ int32_t surfaceRotation) override;
+
+ TouchAffineTransformation getTouchAffineTransformation(JNIEnv* env, jfloatArray matrixArr);
/* --- InputDispatcherPolicyInterface implementation --- */
diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp
index a8aab2a..a72b07c 100644
--- a/tests/Input/Android.bp
+++ b/tests/Input/Android.bp
@@ -6,6 +6,7 @@
static_libs: [
"androidx.test.ext.junit",
"androidx.test.rules",
+ "truth-prebuilt",
"ub-uiautomator",
],
test_suites: ["device-tests"],
diff --git a/tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt b/tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt
new file mode 100644
index 0000000..f919a3e
--- /dev/null
+++ b/tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.input
+
+import android.graphics.FrameInfo
+import android.os.SystemClock
+import android.view.ViewFrameInfo
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+
+class ViewFrameInfoTest {
+ companion object {
+ private const val TAG = "ViewFrameInfoTest"
+ }
+ private val mViewFrameInfo = ViewFrameInfo()
+ private var mTimeStarted: Long = 0
+
+ @Before
+ fun setUp() {
+ mViewFrameInfo.reset()
+ mViewFrameInfo.updateOldestInputEvent(10)
+ mViewFrameInfo.updateNewestInputEvent(20)
+ mViewFrameInfo.flags = mViewFrameInfo.flags or FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED
+ mTimeStarted = SystemClock.uptimeNanos()
+ mViewFrameInfo.markDrawStart()
+ }
+
+ @Test
+ fun testPopulateFields() {
+ assertThat(mViewFrameInfo.drawStart).isGreaterThan(mTimeStarted)
+ assertThat(mViewFrameInfo.oldestInputEventTime).isEqualTo(10)
+ assertThat(mViewFrameInfo.newestInputEventTime).isEqualTo(20)
+ assertThat(mViewFrameInfo.flags).isEqualTo(FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED)
+ }
+
+ @Test
+ fun testReset() {
+ mViewFrameInfo.reset()
+ // Ensure that the original object is reset correctly
+ assertThat(mViewFrameInfo.drawStart).isEqualTo(0)
+ assertThat(mViewFrameInfo.oldestInputEventTime).isEqualTo(0)
+ assertThat(mViewFrameInfo.newestInputEventTime).isEqualTo(0)
+ assertThat(mViewFrameInfo.flags).isEqualTo(0)
+ }
+
+ @Test
+ fun testUpdateFrameInfoFromViewFrameInfo() {
+ val frameInfo = FrameInfo()
+ // By default, all values should be zero
+ assertThat(frameInfo.frameInfo[FrameInfo.OLDEST_INPUT_EVENT]).isEqualTo(0)
+ assertThat(frameInfo.frameInfo[FrameInfo.NEWEST_INPUT_EVENT]).isEqualTo(0)
+ assertThat(frameInfo.frameInfo[FrameInfo.FLAGS]).isEqualTo(0)
+ assertThat(frameInfo.frameInfo[FrameInfo.DRAW_START]).isEqualTo(0)
+
+ // The values inside FrameInfo should match those from ViewFrameInfo after we update them
+ mViewFrameInfo.populateFrameInfo(frameInfo)
+ assertThat(frameInfo.frameInfo[FrameInfo.OLDEST_INPUT_EVENT]).isEqualTo(10)
+ assertThat(frameInfo.frameInfo[FrameInfo.NEWEST_INPUT_EVENT]).isEqualTo(20)
+ assertThat(frameInfo.frameInfo[FrameInfo.FLAGS]).isEqualTo(
+ FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED)
+ assertThat(frameInfo.frameInfo[FrameInfo.DRAW_START]).isGreaterThan(mTimeStarted)
+ }
+}
\ No newline at end of file