add SurfaceView support for skottie on android

Change-Id: I62fe20cdaf735d09552349efb9a21b1ea72bd973
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/310136
Reviewed-by: Derek Sollenberger <djsollen@google.com>
Commit-Queue: Jorge Betancourt <jmbetancourt@google.com>
diff --git a/platform_tools/android/apps/skottie/skottielib/src/main/cpp/native-lib.cpp b/platform_tools/android/apps/skottie/skottielib/src/main/cpp/native-lib.cpp
index cce17d3..a612b41 100644
--- a/platform_tools/android/apps/skottie/skottielib/src/main/cpp/native-lib.cpp
+++ b/platform_tools/android/apps/skottie/skottielib/src/main/cpp/native-lib.cpp
@@ -178,7 +178,8 @@
                                                                      jlong nativeProxy, jint width,
                                                                      jint height,
                                                                      jboolean wideColorGamut,
-                                                                     jfloat progress) {
+                                                                     jfloat progress,
+                                                                     jint backgroundColor) {
     ATRACE_NAME("SkottieDrawFrame");
     if (!nativeProxy) {
         return false;
@@ -220,7 +221,7 @@
             nullptr, &props));
 
     auto canvas = renderTarget->getCanvas();
-    canvas->clear(SK_ColorTRANSPARENT);
+    canvas->clear(backgroundColor);
 
     SkAutoCanvasRestore acr(canvas, true);
     SkRect bounds = SkRect::MakeWH(width, height);
diff --git a/platform_tools/android/apps/skottie/skottielib/src/main/java/org/skia/skottie/SkottieRunner.java b/platform_tools/android/apps/skottie/skottielib/src/main/java/org/skia/skottie/SkottieRunner.java
index 925cce7..56cc117 100644
--- a/platform_tools/android/apps/skottie/skottielib/src/main/java/org/skia/skottie/SkottieRunner.java
+++ b/platform_tools/android/apps/skottie/skottielib/src/main/java/org/skia/skottie/SkottieRunner.java
@@ -14,6 +14,8 @@
 import android.os.HandlerThread;
 import android.util.Log;
 import android.view.Choreographer;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
 import android.view.TextureView;
 
 import java.io.ByteArrayOutputStream;
@@ -82,12 +84,21 @@
     }
 
     /**
+     * Create a new animation by feeding data from "is" and replaying in a SurfaceView.
+     * State is controlled internally by SurfaceHolder.
+     */
+    public SkottieAnimation createAnimation(SurfaceView view, InputStream is, int backgroundColor) {
+        return new SkottieAnimationImpl(view, is, backgroundColor);
+    }
+
+    /**
      * Pass a new SurfaceTexture: use this method only if managing TextureView outside
      * SkottieRunner.
      */
     public void updateAnimationSurface(Animatable animation, SurfaceTexture surfaceTexture,
                                        int width, int height) {
-        ((SkottieAnimationImpl) animation).updateSurface(surfaceTexture, width, height);
+        ((SkottieAnimationImpl) animation).setSurfaceTexture(surfaceTexture);
+        ((SkottieAnimationImpl) animation).updateSurface(width, height);
     }
 
     private SkottieRunner()
@@ -262,28 +273,44 @@
     }
 
     private class SkottieAnimationImpl implements SkottieAnimation, Choreographer.FrameCallback,
-            TextureView.SurfaceTextureListener {
+            TextureView.SurfaceTextureListener, SurfaceHolder.Callback {
         boolean mIsRunning = false;
         SurfaceTexture mSurfaceTexture;
         EGLSurface mEglSurface;
         boolean mNewSurface = false;
+        SurfaceHolder mSurfaceHolder;
+        boolean mValidSurface = false;
 
         private int mSurfaceWidth = 0;
         private int mSurfaceHeight = 0;
+        private int mBackgroundColor;
         private long mNativeProxy;
         private long mDuration;  // duration in ms of the animation
         private float mProgress; // animation progress in the range of 0.0f to 1.0f
         private long mAnimationStartTime; // time in System.nanoTime units, when started
 
         SkottieAnimationImpl(SurfaceTexture surfaceTexture, InputStream is) {
-            init(surfaceTexture, is);
+            init(is);
+            mSurfaceTexture = surfaceTexture;
         }
 
         SkottieAnimationImpl(TextureView view, InputStream is) {
-            init(view.getSurfaceTexture(), is);
+            init(is);
+            mSurfaceTexture = view.getSurfaceTexture();
             view.setSurfaceTextureListener(this);
         }
 
+        SkottieAnimationImpl(SurfaceView view, InputStream is, int backgroundColor) {
+            init(is);
+            mSurfaceHolder = view.getHolder();
+            mSurfaceHolder.addCallback(this);
+            mBackgroundColor = backgroundColor;
+        }
+
+        private void setSurfaceTexture(SurfaceTexture s) {
+            mSurfaceTexture = s;
+        }
+
         private ByteBuffer convertToByteBuffer(InputStream is) throws IOException {
             if (is instanceof FileInputStream) {
                 FileChannel fileChannel = ((FileInputStream)is).getChannel();
@@ -307,7 +334,7 @@
             return buffer.asReadOnlyBuffer();
         }
 
-        private void init(SurfaceTexture surfaceTexture, InputStream is) {
+        private void init(InputStream is) {
 
             ByteBuffer byteBuffer;
             try {
@@ -319,7 +346,6 @@
 
             long proxy = SkottieRunner.getInstance().getNativeProxy();
             mNativeProxy = nCreateProxy(proxy, byteBuffer);
-            mSurfaceTexture = surfaceTexture;
             mDuration = nGetDuration(mNativeProxy);
             mProgress = 0f;
         }
@@ -335,10 +361,9 @@
             }
         }
 
-        public void updateSurface(SurfaceTexture surfaceTexture, int width, int height) {
+        public void updateSurface(int width, int height) {
             try {
                 runOnGLThread(() -> {
-                    mSurfaceTexture = surfaceTexture;
                     mSurfaceWidth = width;
                     mSurfaceHeight = height;
                     mNewSurface = true;
@@ -468,15 +493,16 @@
                 }
 
                 if (mEglSurface == null) {
+                    // block for Texture Views
                     if (mSurfaceTexture != null) {
                         mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig,
                                 mSurfaceTexture, null);
-                        if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
-                            // If failed to create a surface, log an error and stop the animation
-                            int error = mEgl.eglGetError();
-                            throw new RuntimeException("createWindowSurface failed "
-                                    + GLUtils.getEGLErrorString(error));
-                        }
+                        checkSurface();
+                    // block for Surface Views
+                    } else if (mSurfaceHolder != null) {
+                        mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig,
+                            mSurfaceHolder, null);
+                        checkSurface();
                     }
                 }
 
@@ -490,7 +516,7 @@
                     }
                     // only if nDrawFrames() returns true do we need to swap buffers
                     if(nDrawFrame(mNativeProxy, mSurfaceWidth, mSurfaceHeight, false,
-                            mProgress)) {
+                            mProgress, mBackgroundColor)) {
                         if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
                             int error = mEgl.eglGetError();
                             if (error == EGL10.EGL_BAD_SURFACE
@@ -528,6 +554,16 @@
             }
         }
 
+        private void checkSurface() throws RuntimeException {
+            // ensure eglSurface was created
+            if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
+                // If failed to create a surface, log an error and stop the animation
+                int error = mEgl.eglGetError();
+                throw new RuntimeException("createWindowSurface failed "
+                    + GLUtils.getEGLErrorString(error));
+            }
+        }
+
         @Override
         public void doFrame(long frameTimeNanos) {
             if (mIsRunning) {
@@ -543,14 +579,17 @@
                     mAnimationStartTime += durationNS;  // prevents overflow
                 }
             }
-
-            drawFrame();
+            if (mValidSurface) {
+                drawFrame();
+            }
         }
 
         @Override
         public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
             // will be called on UI thread
-            updateSurface(surface, width, height);
+            mValidSurface = true;
+            mSurfaceTexture = surface;
+            updateSurface(width, height);
         }
 
         @Override
@@ -563,6 +602,7 @@
         public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
             // will be called on UI thread
             onSurfaceTextureAvailable(null, 0, 0);
+            mValidSurface = false;
             return true;
         }
 
@@ -571,10 +611,29 @@
 
         }
 
+        // Inherited from SurfaceHolder
+        @Override
+        public void surfaceCreated(SurfaceHolder holder) {
+            mValidSurface = true;
+        }
+
+        @Override
+        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+            mSurfaceHolder = holder;
+            updateSurface(width, height);
+        }
+
+        @Override
+        public void surfaceDestroyed(SurfaceHolder holder) {
+            mValidSurface = false;
+            surfaceChanged(null, 0, 0, 0);
+        }
+
         private native long nCreateProxy(long runner, ByteBuffer data);
         private native void nDeleteProxy(long nativeProxy);
         private native boolean nDrawFrame(long nativeProxy, int width, int height,
-                                          boolean wideColorGamut, float progress);
+                                          boolean wideColorGamut, float progress,
+                                          int backgroundColor);
         private native long nGetDuration(long nativeProxy);
     }
 
diff --git a/platform_tools/android/apps/skottie/skottielib/src/main/java/org/skia/skottie/SkottieView.java b/platform_tools/android/apps/skottie/skottielib/src/main/java/org/skia/skottie/SkottieView.java
index e32b0d2..0a51f59 100644
--- a/platform_tools/android/apps/skottie/skottielib/src/main/java/org/skia/skottie/SkottieView.java
+++ b/platform_tools/android/apps/skottie/skottielib/src/main/java/org/skia/skottie/SkottieView.java
@@ -25,7 +25,8 @@
 public class SkottieView extends FrameLayout {
 
     private SkottieAnimation mAnimation;
-    private View mbackingView;
+    private View mBackingView;
+    private int mBackgroundColor;
 
     private static final int BACKING_VIEW_TEXTURE = 0;
     private static final int BACKING_VIEW_SURFACE = 1;
@@ -39,17 +40,17 @@
         try {
             switch (a.getInteger(R.styleable.SkottieView_backing_view, -1)) {
                 case BACKING_VIEW_TEXTURE:
-                    mbackingView = new TextureView(context);
-                    ((TextureView) mbackingView).setOpaque(false);
+                    mBackingView = new TextureView(context);
+                    ((TextureView) mBackingView).setOpaque(false);
                     break;
                 case BACKING_VIEW_SURFACE:
-                    mbackingView = new SurfaceView(context);
-                    int bg = a.getColor(R.styleable.SkottieView_background_color, -1);
-                    if (bg == -1) {
+                    mBackingView = new SurfaceView(context);
+                    mBackgroundColor = a.getColor(R.styleable.SkottieView_background_color, -1);
+                    if (mBackgroundColor == -1) {
                         throw new RuntimeException("background_color attribute "
                             + "needed for SurfaceView");
                     }
-                    if (Color.alpha(bg) != 255) {
+                    if (Color.alpha(mBackgroundColor) != 255) {
                         throw new RuntimeException("background_color cannot be transparent");
                     }
                     break;
@@ -69,37 +70,44 @@
         // create the backing view
         if (builder.advancedFeatures) {
             // backing view must be SurfaceView
-            mbackingView = new SurfaceView(context);
+            mBackingView = new SurfaceView(context);
         } else {
-            mbackingView = new TextureView(context);
-            ((TextureView) mbackingView).setOpaque(false);
+            mBackingView = new TextureView(context);
+            ((TextureView) mBackingView).setOpaque(false);
         }
         initBackingView();
     }
 
     private void initBackingView() {
-        mbackingView.setLayoutParams(new ViewGroup.LayoutParams(
+        mBackingView.setLayoutParams(new ViewGroup.LayoutParams(
             ViewGroup.LayoutParams.MATCH_PARENT,
             ViewGroup.LayoutParams.MATCH_PARENT));
-        addView(mbackingView);
+        addView(mBackingView);
     }
 
     //TODO handle SurfaceView
     public void setSource(InputStream inputStream) {
-        mAnimation = SkottieRunner.getInstance()
-            .createAnimation(((TextureView) mbackingView), inputStream);
+        mAnimation = setSourceHelper(inputStream);
     }
 
     public void setSource(int resId) {
-        InputStream inputStream = mbackingView.getResources().openRawResource(resId);
-        mAnimation = SkottieRunner.getInstance()
-            .createAnimation(((TextureView) mbackingView), inputStream);
+        InputStream inputStream = mBackingView.getResources().openRawResource(resId);
+        mAnimation = setSourceHelper(inputStream);
     }
 
     public void setSource(Context context, Uri uri) throws FileNotFoundException {
         InputStream inputStream = context.getContentResolver().openInputStream(uri);
-        mAnimation = SkottieRunner.getInstance()
-            .createAnimation(((TextureView) mbackingView), inputStream);
+        mAnimation = setSourceHelper(inputStream);
+    }
+
+    private SkottieAnimation setSourceHelper(InputStream inputStream) {
+        if (mBackingView instanceof TextureView) {
+            return SkottieRunner.getInstance()
+                .createAnimation(((TextureView) mBackingView), inputStream);
+        } else {
+            return SkottieRunner.getInstance()
+                .createAnimation(((SurfaceView) mBackingView), inputStream, mBackgroundColor);
+        }
     }
 
     protected SkottieAnimation getSkottieAnimation() {