Wire up SurfaceView stretch effect
Bug: 179047472
Test: StretchySurfaceViewActivity
Change-Id: I7f3d582cc66fb732a557e9332edc6d186db2335c
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 66b9617..3e62fde 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -141,6 +141,9 @@
int layerStack);
private static native void nativeSetBlurRegions(long transactionObj, long nativeObj,
float[][] regions, int length);
+ private static native void nativeSetStretchEffect(long transactionObj, long nativeObj,
+ float left, float top, float right, float bottom, float vecX, float vecY,
+ float maxStretchAmount);
private static native boolean nativeClearContentFrameStats(long nativeObject);
private static native boolean nativeGetContentFrameStats(long nativeObject, WindowContentFrameStats outStats);
@@ -2951,6 +2954,17 @@
/**
* @hide
*/
+ public Transaction setStretchEffect(SurfaceControl sc, float left, float top, float right,
+ float bottom, float vecX, float vecY, float maxStretchAmount) {
+ checkPreconditions(sc);
+ nativeSetStretchEffect(mNativeObject, sc.mNativeObject, left, top, right, bottom,
+ vecX, vecY, maxStretchAmount);
+ return this;
+ }
+
+ /**
+ * @hide
+ */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O)
public Transaction setLayerStack(SurfaceControl sc, int layerStack) {
checkPreconditions(sc);
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 6eba83f..ec7e4c1 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1444,6 +1444,14 @@
}
@Override
+ public void applyStretch(long frameNumber, float left, float top, float right,
+ float bottom, float vecX, float vecY, float maxStretch) {
+ mRtTransaction.setStretchEffect(mSurfaceControl, left, top, right, bottom, vecX, vecY,
+ maxStretch);
+ applyRtTransaction(frameNumber);
+ }
+
+ @Override
public void positionLost(long frameNumber) {
if (DEBUG) {
Log.d(TAG, String.format("%d windowPositionLost, frameNr = %d",
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 7a3366a..ffd1380 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -580,6 +580,15 @@
transaction->setBlurRegions(ctrl, blurRegionVector);
}
+static void nativeSetStretchEffect(JNIEnv* env, jclass clazz, jlong transactionObj,
+ jlong nativeObject, jfloat left, jfloat top, jfloat right,
+ jfloat bottom, jfloat vecX, jfloat vecY,
+ jfloat maxStretchAmount) {
+ auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+ SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
+ transaction->setStretchEffect(ctrl, left, top, right, bottom, vecX, vecY, maxStretchAmount);
+}
+
static void nativeSetSize(JNIEnv* env, jclass clazz, jlong transactionObj,
jlong nativeObject, jint w, jint h) {
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -1754,6 +1763,8 @@
(void*)nativeSetLayerStack },
{"nativeSetBlurRegions", "(JJ[[FI)V",
(void*)nativeSetBlurRegions },
+ {"nativeSetStretchEffect", "(JJFFFFFFF)V",
+ (void*) nativeSetStretchEffect },
{"nativeSetShadowRadius", "(JJF)V",
(void*)nativeSetShadowRadius },
{"nativeSetFrameRate", "(JJFIZ)V",
diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java
index 4a92cf1..f6f770b 100644
--- a/graphics/java/android/graphics/RenderNode.java
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -272,6 +272,16 @@
void positionChanged(long frameNumber, int left, int top, int right, int bottom);
/**
+ * Call to apply a stretch effect to any child SurfaceControl layers
+ *
+ * TODO: Fold this into positionChanged & have HWUI do the ASurfaceControl calls?
+ *
+ * @hide
+ */
+ default void applyStretch(long frameNumber, float left, float top, float right,
+ float bottom, float vecX, float vecY, float maxStretch) { }
+
+ /**
* Called by native on RenderThread to notify that the view is no longer in the
* draw tree. UI thread is blocked at this point.
*
@@ -312,6 +322,14 @@
pul.positionLost(frameNumber);
}
}
+
+ @Override
+ public void applyStretch(long frameNumber, float left, float top, float right, float bottom,
+ float vecX, float vecY, float maxStretch) {
+ for (PositionUpdateListener pul : mListeners) {
+ pul.applyStretch(frameNumber, left, top, right, bottom, vecX, vecY, maxStretch);
+ }
+ }
}
/**
@@ -707,7 +725,7 @@
if (1.0 < vecY || vecY < -1.0) {
throw new IllegalArgumentException("vecY must be in the range [-1, 1], was " + vecY);
}
- if (top <= bottom || right <= left) {
+ if (top >= bottom || left >= right) {
throw new IllegalArgumentException(
"Stretch region must not be empty, got "
+ new RectF(left, top, right, bottom).toString());
diff --git a/libs/hwui/DamageAccumulator.cpp b/libs/hwui/DamageAccumulator.cpp
index b39f4f2..0bf9480 100644
--- a/libs/hwui/DamageAccumulator.cpp
+++ b/libs/hwui/DamageAccumulator.cpp
@@ -249,5 +249,20 @@
mHead->pendingDirty.setEmpty();
}
+const StretchEffect* DamageAccumulator::findNearestStretchEffect() const {
+ DirtyStack* frame = mHead;
+ while (frame->prev != frame) {
+ frame = frame->prev;
+ if (frame->type == TransformRenderNode) {
+ const auto& effect =
+ frame->renderNode->properties().layerProperties().getStretchEffect();
+ if (!effect.isEmpty()) {
+ return &effect;
+ }
+ }
+ }
+ return nullptr;
+}
+
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/DamageAccumulator.h b/libs/hwui/DamageAccumulator.h
index 2faa9d0..89ee0e3 100644
--- a/libs/hwui/DamageAccumulator.h
+++ b/libs/hwui/DamageAccumulator.h
@@ -35,6 +35,7 @@
struct DirtyStack;
class RenderNode;
class Matrix4;
+class StretchEffect;
class DamageAccumulator {
PREVENT_COPY_AND_ASSIGN(DamageAccumulator);
@@ -62,6 +63,8 @@
void finish(SkRect* totalDirty);
+ const StretchEffect* findNearestStretchEffect() const;
+
private:
void pushCommon();
void applyMatrix4Transform(DirtyStack* frame);
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 44f54ee..f5b2675 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -226,6 +226,9 @@
if (!mProperties.getAllowForceDark()) {
info.disableForceDark++;
}
+ if (!mProperties.layerProperties().getStretchEffect().isEmpty()) {
+ info.stretchEffectCount++;
+ }
uint32_t animatorDirtyMask = 0;
if (CC_LIKELY(info.runAnimations)) {
@@ -267,6 +270,9 @@
if (!mProperties.getAllowForceDark()) {
info.disableForceDark--;
}
+ if (!mProperties.layerProperties().getStretchEffect().isEmpty()) {
+ info.stretchEffectCount--;
+ }
info.damageAccumulator->popTransform();
}
diff --git a/libs/hwui/RenderProperties.cpp b/libs/hwui/RenderProperties.cpp
index 8fba9cf..0589f13 100644
--- a/libs/hwui/RenderProperties.cpp
+++ b/libs/hwui/RenderProperties.cpp
@@ -70,6 +70,7 @@
setXferMode(other.xferMode());
setColorFilter(other.getColorFilter());
setImageFilter(other.getImageFilter());
+ mStretchEffect = other.mStretchEffect;
return *this;
}
diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h
index f2481f8..cc9094c 100644
--- a/libs/hwui/TreeInfo.h
+++ b/libs/hwui/TreeInfo.h
@@ -98,6 +98,8 @@
const SkISize screenSize;
+ int stretchEffectCount = 0;
+
struct Out {
bool hasFunctors = false;
// This is only updated if evaluateAnimations is true
diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp
index 8023968..5f60437 100644
--- a/libs/hwui/jni/android_graphics_RenderNode.cpp
+++ b/libs/hwui/jni/android_graphics_RenderNode.cpp
@@ -25,6 +25,7 @@
#include <renderthread/CanvasContext.h>
#endif
#include <TreeInfo.h>
+#include <effects/StretchEffect.h>
#include <hwui/Paint.h>
#include <utils/TraceUtils.h>
@@ -549,6 +550,7 @@
// ----------------------------------------------------------------------------
jmethodID gPositionListener_PositionChangedMethod;
+jmethodID gPositionListener_ApplyStretchMethod;
jmethodID gPositionListener_PositionLostMethod;
static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
@@ -571,6 +573,11 @@
Matrix4 transform;
info.damageAccumulator->computeCurrentTransform(&transform);
const RenderProperties& props = node.properties();
+
+ if (info.stretchEffectCount) {
+ handleStretchEffect(info, transform);
+ }
+
uirenderer::Rect bounds(props.getWidth(), props.getHeight());
transform.mapRect(bounds);
@@ -613,7 +620,7 @@
JNIEnv* env = jnienv();
jobject localref = env->NewLocalRef(mWeakRef);
if (CC_UNLIKELY(!localref)) {
- jnienv()->DeleteWeakGlobalRef(mWeakRef);
+ env->DeleteWeakGlobalRef(mWeakRef);
mWeakRef = nullptr;
return;
}
@@ -634,6 +641,32 @@
return env;
}
+ void handleStretchEffect(const TreeInfo& info, const Matrix4& transform) {
+ // Search up to find the nearest stretcheffect parent
+ const StretchEffect* effect = info.damageAccumulator->findNearestStretchEffect();
+ if (!effect) {
+ return;
+ }
+
+ uirenderer::Rect area = effect->stretchArea;
+ transform.mapRect(area);
+ JNIEnv* env = jnienv();
+
+ jobject localref = env->NewLocalRef(mWeakRef);
+ if (CC_UNLIKELY(!localref)) {
+ env->DeleteWeakGlobalRef(mWeakRef);
+ mWeakRef = nullptr;
+ return;
+ }
+#ifdef __ANDROID__ // Layoutlib does not support CanvasContext
+ env->CallVoidMethod(localref, gPositionListener_ApplyStretchMethod,
+ info.canvasContext.getFrameNumber(), area.left, area.top,
+ area.right, area.bottom, effect->stretchDirection.fX,
+ effect->stretchDirection.fY, effect->maxStretchAmount);
+#endif
+ env->DeleteLocalRef(localref);
+ }
+
void doUpdatePositionAsync(jlong frameNumber, jint left, jint top,
jint right, jint bottom) {
ATRACE_NAME("Update SurfaceView position");
@@ -775,6 +808,8 @@
jclass clazz = FindClassOrDie(env, "android/graphics/RenderNode$PositionUpdateListener");
gPositionListener_PositionChangedMethod = GetMethodIDOrDie(env, clazz,
"positionChanged", "(JIIII)V");
+ gPositionListener_ApplyStretchMethod =
+ GetMethodIDOrDie(env, clazz, "applyStretch", "(JFFFFFFF)V");
gPositionListener_PositionLostMethod = GetMethodIDOrDie(env, clazz,
"positionLost", "(J)V");
return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index c6c67fe..62ccb1a0 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -400,6 +400,15 @@
</intent-filter>
</activity>
+ <activity android:name="StretchySurfaceViewActivity"
+ android:label="SurfaceView/Stretchy Movement"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="com.android.test.hwui.TEST"/>
+ </intent-filter>
+ </activity>
+
<activity android:name="GetBitmapSurfaceViewActivity"
android:label="SurfaceView/GetBitmap with Camera source"
android:exported="true">
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/PositionListenerActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/PositionListenerActivity.java
index 818d899..65d7363 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/PositionListenerActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/PositionListenerActivity.java
@@ -19,8 +19,13 @@
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
+import android.graphics.PointF;
+import android.graphics.RecordingCanvas;
+import android.graphics.Rect;
+import android.graphics.RectF;
import android.graphics.RenderNode;
import android.os.Bundle;
+import android.view.MotionEvent;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.ScrollView;
@@ -38,7 +43,46 @@
ProgressBar spinner = new ProgressBar(this, null, android.R.attr.progressBarStyleLarge);
layout.addView(spinner);
- ScrollView scrollingThing = new ScrollView(this);
+ ScrollView scrollingThing = new ScrollView(this) {
+ int setting = 0;
+ PointF opts[] = new PointF[] {
+ new PointF(0, 0),
+ new PointF(0, -1f),
+ new PointF(1f, 0),
+ new PointF(0, 1f),
+ new PointF(-1f, 0),
+ new PointF(-1f, 1f),
+ };
+ {
+ setWillNotDraw(false);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
+ setting = (setting + 1) % opts.length;
+ invalidate();
+ }
+ return super.onTouchEvent(ev);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ RenderNode node = ((RecordingCanvas) canvas).mNode;
+ PointF dir = opts[setting];
+ float maxStretchAmount = 100f;
+ // Although we could do this in a single call, the real one won't be - so mimic that
+ if (dir.x != 0f) {
+ node.stretch(0f, 0f, (float) getWidth(), (float) getHeight(),
+ dir.x, 0f, maxStretchAmount);
+ }
+ if (dir.y != 0f) {
+ node.stretch(0f, 0f, (float) getWidth(), (float) getHeight(),
+ 0f, dir.y, maxStretchAmount);
+ }
+ }
+ };
scrollingThing.addView(new MyPositionReporter(this));
layout.addView(scrollingThing);
@@ -49,6 +93,11 @@
RenderNode mNode;
int mCurrentCount = 0;
int mTranslateY = 0;
+ Rect mPosition = new Rect();
+ RectF mStretchArea = new RectF();
+ float mStretchX = 0.0f;
+ float mStretchY = 0.0f;
+ float mStretchMax = 0.0f;
MyPositionReporter(Context c) {
super(c);
@@ -78,18 +127,36 @@
canvas.drawRenderNode(mNode);
}
+ void updateText() {
+ setText(String.format("%d: Position %s, stretch area %s, vec %f,%f, amount %f",
+ mCurrentCount, mPosition.toShortString(), mStretchArea.toShortString(),
+ mStretchX, mStretchY, mStretchMax));
+ }
+
@Override
public void positionChanged(long frameNumber, int left, int top, int right, int bottom) {
- post(() -> {
+ getHandler().postAtFrontOfQueue(() -> {
mCurrentCount++;
- setText(String.format("%d: Position [%d, %d, %d, %d]", mCurrentCount,
- left, top, right, bottom));
+ mPosition.set(left, top, right, bottom);
+ updateText();
+ });
+ }
+
+ @Override
+ public void applyStretch(long frameNumber, float left, float top, float right, float bottom,
+ float vecX, float vecY, float maxStretch) {
+ getHandler().postAtFrontOfQueue(() -> {
+ mStretchArea.set(left, top, right, bottom);
+ mStretchX = vecX;
+ mStretchY = vecY;
+ mStretchMax = maxStretch;
+ updateText();
});
}
@Override
public void positionLost(long frameNumber) {
- post(() -> {
+ getHandler().postAtFrontOfQueue(() -> {
mCurrentCount++;
setText(mCurrentCount + " No position");
});
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/StretchySurfaceViewActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/StretchySurfaceViewActivity.java
new file mode 100644
index 0000000..d604244
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/StretchySurfaceViewActivity.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2021 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.hwui;
+
+import android.animation.ObjectAnimator;
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.RecordingCanvas;
+import android.graphics.RenderNode;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.SurfaceHolder;
+import android.view.SurfaceHolder.Callback;
+import android.view.SurfaceView;
+import android.view.animation.LinearInterpolator;
+import android.widget.FrameLayout;
+
+public class StretchySurfaceViewActivity extends Activity implements Callback {
+ SurfaceView mSurfaceView;
+ ObjectAnimator mAnimator;
+
+ class MySurfaceView extends SurfaceView {
+ boolean mSlow;
+ boolean mScaled;
+ int mToggle = 0;
+
+ public MySurfaceView(Context context) {
+ super(context);
+ setOnClickListener(v -> {
+ mToggle = (mToggle + 1) % 4;
+ mSlow = (mToggle & 0x2) != 0;
+ mScaled = (mToggle & 0x1) != 0;
+
+ mSurfaceView.setScaleX(mScaled ? 1.6f : 1f);
+ mSurfaceView.setScaleY(mScaled ? 0.8f : 1f);
+
+ setTitle("Slow=" + mSlow + ", scaled=" + mScaled);
+ invalidate();
+ });
+ setWillNotDraw(false);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ super.draw(canvas);
+ if (mSlow) {
+ try {
+ Thread.sleep(16);
+ } catch (InterruptedException e) {}
+ }
+ }
+
+ public void setMyTranslationY(float ty) {
+ setTranslationY(ty);
+ if (mSlow) {
+ invalidate();
+ }
+ }
+
+ public float getMyTranslationY() {
+ return getTranslationY();
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ FrameLayout content = new FrameLayout(this) {
+ {
+ setWillNotDraw(false);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ Paint paint = new Paint();
+ paint.setAntiAlias(true);
+ paint.setColor(Color.RED);
+ paint.setStyle(Paint.Style.STROKE);
+ paint.setStrokeWidth(10f);
+ canvas.drawLine(0f, 0f, getWidth(), getHeight(), paint);
+ super.onDraw(canvas);
+
+ RenderNode node = ((RecordingCanvas) canvas).mNode;
+ node.stretch(0f, 0f, getWidth(), getHeight() / 2f, 0f, 1f, 400f);
+ }
+ };
+
+ mSurfaceView = new MySurfaceView(this);
+ mSurfaceView.getHolder().addCallback(this);
+
+ final float density = getResources().getDisplayMetrics().density;
+ int size = (int) (200 * density);
+
+ content.addView(mSurfaceView, new FrameLayout.LayoutParams(
+ size, size, Gravity.CENTER_HORIZONTAL | Gravity.TOP));
+ mAnimator = ObjectAnimator.ofFloat(mSurfaceView, "myTranslationY",
+ 0, size);
+ mAnimator.setRepeatMode(ObjectAnimator.REVERSE);
+ mAnimator.setRepeatCount(ObjectAnimator.INFINITE);
+ mAnimator.setDuration(1000);
+ mAnimator.setInterpolator(new LinearInterpolator());
+ setContentView(content);
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ Canvas canvas = holder.lockCanvas();
+ canvas.drawColor(Color.WHITE);
+
+ Paint paint = new Paint();
+ paint.setAntiAlias(true);
+ paint.setColor(Color.GREEN);
+ paint.setStyle(Paint.Style.STROKE);
+ paint.setStrokeWidth(10f);
+ canvas.drawLine(0, 0, width, height, paint);
+
+ holder.unlockCanvasAndPost(canvas);
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mAnimator.start();
+ }
+
+ @Override
+ protected void onPause() {
+ mAnimator.pause();
+ super.onPause();
+ }
+}