Render fireflies with OpenGL.

- Show a static (scaling in) background in case we're not hw-accelerated.
- Modify the cloning animation a bit.

Change-Id: I808238fb515c2c8d7832a4ab8bc70a37e1198de5
diff --git a/assets/star.png b/assets/star.png
new file mode 100644
index 0000000..77b9ca9
--- /dev/null
+++ b/assets/star.png
Binary files differ
diff --git a/res/drawable-hdpi/back.png b/res/drawable-hdpi/back.png
new file mode 100644
index 0000000..dcad41d
--- /dev/null
+++ b/res/drawable-hdpi/back.png
Binary files differ
diff --git a/res/layout/screenshot.xml b/res/layout/screenshot.xml
index 180d045..9e62af7 100644
--- a/res/layout/screenshot.xml
+++ b/res/layout/screenshot.xml
@@ -18,6 +18,18 @@
     android:layout_height="match_parent"
     android:background="#FF000000"
 >
+    <TextureView android:id="@+id/fireflies"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+    />
+    <ImageView android:id="@+id/back"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:adjustViewBounds="true"
+        android:src="@drawable/back"
+        android:scaleType="centerCrop"
+        android:visibility="gone"
+    />
     <RelativeLayout android:layout_width="match_parent"
         android:layout_height="match_parent"
     >
@@ -34,12 +46,12 @@
             android:textColor="?android:attr/textColorPrimary"
         />
     </RelativeLayout>
-    <ImageView android:id="@+id/screenshot"
+    <ImageView android:id="@+id/clone"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:adjustViewBounds="true"
     />
-    <ImageView android:id="@+id/clone"
+    <ImageView android:id="@+id/screenshot"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:adjustViewBounds="true"
diff --git a/src/com/android/nfc/FireflyRenderThread.java b/src/com/android/nfc/FireflyRenderThread.java
new file mode 100644
index 0000000..13b9a12
--- /dev/null
+++ b/src/com/android/nfc/FireflyRenderThread.java
@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) 2011 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.nfc;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.SurfaceTexture;
+import android.opengl.GLUtils;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.egl.EGLSurface;
+import javax.microedition.khronos.opengles.GL10;
+
+public class FireflyRenderThread extends Thread {
+    private static final String LOG_TAG = "NfcFireflyThread";
+
+    SurfaceTexture mSurface;
+
+    EGL10 mEgl;
+    EGLDisplay mEglDisplay;
+    EGLConfig mEglConfig;
+    EGLContext mEglContext;
+    EGLSurface mEglSurface;
+    GL10 mGL;
+
+    static final int NUM_FIREFLIES = 100;
+    static final int PIXELS_PER_SECOND = 50; // Speed of fireflies
+
+    static final int[] sEglConfig = {
+        EGL10.EGL_RED_SIZE, 8,
+        EGL10.EGL_GREEN_SIZE, 8,
+        EGL10.EGL_BLUE_SIZE, 8,
+        EGL10.EGL_ALPHA_SIZE, 0,
+        EGL10.EGL_DEPTH_SIZE, 0,
+        EGL10.EGL_STENCIL_SIZE, 0,
+        EGL10.EGL_NONE
+    };
+
+    // Vertices for drawing a 32x32 rect
+    static final float mVertices[] = {
+        0.0f,  0.0f, 0.0f,  // 0, Top Left
+        0.0f,  32.0f, 0.0f, // 1, Bottom Left
+        32.0f, 32.0f, 0.0f, // 2, Bottom Right
+        32.0f, 0.0f, 0.0f,  // 3, Top Right
+    };
+
+    // Mapping coordinates for the texture
+    static final float mTextCoords[] = {
+        0.0f, 0.0f,
+        1.0f, 0.0f,
+        1.0f, 1.0f,
+        0.0f, 1.0f
+    };
+
+    // Connecting order (draws a square)
+    static final short[] mIndices = { 0, 1, 2, 0, 2, 3 };
+
+    // Buffer holding the vertices
+    FloatBuffer mVertexBuffer;
+
+    // Buffer holding the indices
+    ShortBuffer mIndexBuffer;
+
+    // Buffer holding the texture mapping coordinates
+    FloatBuffer mTextureBuffer;
+
+    // Holding the handle to the texture
+    int mTextureId;
+
+    final Context mContext;
+    final int mDisplayWidth;
+    final int mDisplayHeight;
+
+    Firefly[] mFireflies;
+    long mStartTime;
+
+    // Read/written by multiple threads
+    volatile boolean mFinished;
+    volatile boolean mFadeOut;
+
+    public FireflyRenderThread(Context context, SurfaceTexture surface, int width, int height) {
+        mSurface = surface;
+        mContext = context;
+        mDisplayWidth = width;
+        mDisplayHeight = height;
+        mFinished = false;
+    }
+
+    public void finish() {
+        mFinished = true;
+    }
+
+    public void fadeOut() {
+        mFadeOut = true;
+    }
+
+    void initShapes() {
+        // First, build the vertex, texture and index buffers
+        ByteBuffer vbb = ByteBuffer.allocateDirect(mVertices.length * 4); // Float => 4 bytes
+        vbb.order(ByteOrder.nativeOrder());
+        mVertexBuffer = vbb.asFloatBuffer();
+        mVertexBuffer.put(mVertices);
+        mVertexBuffer.position(0);
+
+        ByteBuffer ibb = ByteBuffer.allocateDirect(mIndices.length * 2); // Short => 2 bytes
+        ibb.order(ByteOrder.nativeOrder());
+        mIndexBuffer = ibb.asShortBuffer();
+        mIndexBuffer.put(mIndices);
+        mIndexBuffer.position(0);
+
+        ByteBuffer tbb = ByteBuffer.allocateDirect(mTextCoords.length * 4);
+        tbb.order(ByteOrder.nativeOrder());
+        mTextureBuffer = tbb.asFloatBuffer();
+        mTextureBuffer.put(mTextCoords);
+        mTextureBuffer.position(0);
+
+        mFadeOut = false;
+
+        mFireflies = new Firefly[NUM_FIREFLIES];
+        for (int i = 0; i < NUM_FIREFLIES; i++) {
+            mFireflies[i] = new Firefly();
+        }
+        loadStarTexture();
+    }
+
+    void loadStarTexture() {
+        int[] textureIds = new int[1];
+        mGL.glGenTextures(1, textureIds, 0);
+        mTextureId = textureIds[0];
+
+        InputStream in = null;
+        try {
+            // Remember that both texture dimensions must be a power of 2!
+            in = mContext.getAssets().open("star.png");
+
+            Bitmap bitmap = BitmapFactory.decodeStream(in);
+            mGL.glBindTexture(GL10.GL_TEXTURE_2D, mTextureId);
+
+            mGL.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
+            mGL.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
+
+            GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
+
+            bitmap.recycle();
+
+        } catch (IOException e) {
+            Log.e(LOG_TAG, "IOException opening assets.");
+        } finally {
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (IOException e) { }
+            }
+        }
+    }
+
+    @Override
+    public void run() {
+        if (!initGL()) {
+            finishGL();
+            return;
+        }
+
+        mGL.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+
+        initShapes();
+
+        mGL.glViewport(0, 0, mDisplayWidth, mDisplayHeight);
+
+        // make adjustments for screen ratio
+        mGL.glMatrixMode(GL10.GL_PROJECTION);
+        mGL.glLoadIdentity();
+        mGL.glOrthof(0, mDisplayWidth, mDisplayHeight, 0, -1, 1);
+
+        // Switch back to modelview
+        mGL.glMatrixMode(GL10.GL_MODELVIEW);
+        mGL.glLoadIdentity();
+
+        // Reset firefly models
+        for (Firefly firefly : mFireflies) {
+            firefly.reset();
+        }
+
+        while (!mFinished) {
+            long timeElapsedMs = System.currentTimeMillis() - mStartTime;
+            mStartTime = System.currentTimeMillis();
+
+            checkCurrent();
+
+            mGL.glClear(GL10.GL_COLOR_BUFFER_BIT);
+            mGL.glLoadIdentity();
+
+            mGL.glEnable(GL10.GL_TEXTURE_2D);
+
+            mGL.glEnable(GL10.GL_BLEND);
+
+            mGL.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE);
+
+            for (Firefly firefly : mFireflies) {
+                firefly.updatePositionAndScale(timeElapsedMs);
+                firefly.draw();
+            }
+
+            if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
+                Log.e(LOG_TAG, "Could not swap buffers");
+                mFinished = true;
+            }
+
+            long elapsed = System.currentTimeMillis() - mStartTime;
+            try {
+                Thread.sleep(Math.max(30 - elapsed, 0));
+            } catch (InterruptedException e) {
+
+            }
+        }
+        finishGL();
+    }
+
+    boolean initGL() {
+        // Initialize openGL engine
+        mEgl = (EGL10) EGLContext.getEGL();
+
+        mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+        if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
+            Log.e(LOG_TAG, "eglGetDisplay failed " +
+                    GLUtils.getEGLErrorString(mEgl.eglGetError()));
+            return false;
+        }
+
+        int[] version = new int[2];
+        if (!mEgl.eglInitialize(mEglDisplay, version)) {
+            Log.e(LOG_TAG, "eglInitialize failed " +
+                    GLUtils.getEGLErrorString(mEgl.eglGetError()));
+            return false;
+        }
+
+        mEglConfig = chooseEglConfig();
+        if (mEglConfig == null) {
+            Log.e(LOG_TAG, "eglConfig not initialized.");
+            return false;
+        }
+
+        mEglContext = mEgl.eglCreateContext(mEglDisplay, mEglConfig, EGL10.EGL_NO_CONTEXT, null);
+
+        mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, mSurface, null);
+
+        if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
+            int error = mEgl.eglGetError();
+            Log.e(LOG_TAG,"createWindowSurface returned error");
+            return false;
+        }
+
+        if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
+            Log.e(LOG_TAG, "eglMakeCurrent failed " +
+                    GLUtils.getEGLErrorString(mEgl.eglGetError()));
+            return false;
+        }
+
+        mGL = (GL10) mEglContext.getGL();
+
+        return true;
+    }
+
+    private void finishGL() {
+        if (mEgl == null || mEglDisplay == null) {
+            // Nothing to free
+            return;
+        }
+        // Unbind the current surface and context from the display
+        mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE,
+                EGL10.EGL_NO_CONTEXT);
+        if (mEglContext != null) {
+            mEgl.eglDestroyContext(mEglDisplay, mEglContext);
+        }
+        if (mEglSurface != null) {
+            mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
+        }
+    }
+
+    private void checkCurrent() {
+        if (!mEglContext.equals(mEgl.eglGetCurrentContext()) ||
+                !mEglSurface.equals(mEgl.eglGetCurrentSurface(EGL10.EGL_DRAW))) {
+            if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
+                throw new RuntimeException("eglMakeCurrent failed "
+                        + GLUtils.getEGLErrorString(mEgl.eglGetError()));
+            }
+        }
+    }
+
+    private EGLConfig chooseEglConfig() {
+        int[] configsCount = new int[1];
+        EGLConfig[] configs = new EGLConfig[1];
+        if (!mEgl.eglChooseConfig(mEglDisplay, sEglConfig, configs, 1, configsCount)) {
+            throw new IllegalArgumentException("eglChooseConfig failed " +
+                    GLUtils.getEGLErrorString(mEgl.eglGetError()));
+        } else if (configsCount[0] > 0) {
+            return configs[0];
+        }
+        return null;
+    }
+
+    public class Firefly {
+        static final int STEADY_INTERVAL_MS = 1250; // Time fireflies continue in same direction
+        static final float MIN_SCALE = 0.5f; // The minimum amount fireflies are scaled to
+
+        float mX;
+        float mY;
+        float mAngle;
+        float mScale;
+        float mAlpha;
+        boolean mScalingDown;
+        int mTimeRemaining;
+
+        public Firefly() {
+            reset();
+        }
+
+        void reset() {
+            float randomVal = (float) Math.random();
+
+            mX = randomVal * mDisplayWidth;
+            mY = (float) (Math.random() * mDisplayHeight);
+            mAngle = randomVal * 360f;
+            mScalingDown = (randomVal > 0.5f) ? true : false;
+            mTimeRemaining = (int) (randomVal * STEADY_INTERVAL_MS);
+            mAlpha = 1;
+        }
+
+        public void updatePositionAndScale(long timeElapsedMs) {
+            if (mFadeOut) {
+                // Diminish alpha and return
+                if (mAlpha > 0) {
+                    mAlpha -= timeElapsedMs * 0.003;
+                }
+                return;
+            }
+            mX += Math.cos(Math.toRadians(mAngle)) * timeElapsedMs / 1000 * PIXELS_PER_SECOND;
+            mY += Math.sin(Math.toRadians(mAngle)) * timeElapsedMs / 1000 * PIXELS_PER_SECOND;
+
+            mX %= mDisplayWidth;
+            if (mX < 0) {
+                mX += mDisplayWidth;
+            }
+            mY %= mDisplayHeight;
+            if (mY < 0) {
+                mY += mDisplayHeight;
+            }
+
+            if (mScalingDown) {
+                mScale = 1.0f - (((float)mTimeRemaining / STEADY_INTERVAL_MS) * MIN_SCALE);
+            } else {
+                mScale = MIN_SCALE + (((float)mTimeRemaining / STEADY_INTERVAL_MS) * MIN_SCALE);
+            }
+
+            mTimeRemaining -= timeElapsedMs;
+            if (mTimeRemaining < 0) {
+                // Update our angle, reverse our scaling
+                mAngle = (float) (Math.random() * 360f);
+                mScalingDown = !mScalingDown;
+                mTimeRemaining = STEADY_INTERVAL_MS;
+            }
+        }
+
+        public void draw() {
+            mGL.glLoadIdentity();
+
+            // Counter clockwise winding
+            mGL.glFrontFace(GL10.GL_CCW);
+
+            mGL.glEnableClientState(GL10.GL_VERTEX_ARRAY);
+            mGL.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
+
+            mGL.glVertexPointer(3, GL10.GL_FLOAT, 0, mVertexBuffer);
+            mGL.glTexCoordPointer(2, GL10.GL_FLOAT, 0, mTextureBuffer);
+
+            mGL.glTranslatef(mX, mY, 0);
+            mGL.glScalef(mScale, mScale, 0);
+            mGL.glColor4f(1, 1, 1, mAlpha);
+
+            mGL.glDrawElements(GL10.GL_TRIANGLES, mIndices.length, GL10.GL_UNSIGNED_SHORT,
+                    mIndexBuffer);
+
+            mGL.glColor4f(1, 1, 1, 1);
+            mGL.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
+            mGL.glDisableClientState(GL10.GL_VERTEX_ARRAY);
+        }
+    }
+}
diff --git a/src/com/android/nfc/SendUi.java b/src/com/android/nfc/SendUi.java
index 39b3758..0bb8916 100644
--- a/src/com/android/nfc/SendUi.java
+++ b/src/com/android/nfc/SendUi.java
@@ -31,6 +31,7 @@
 import android.graphics.Canvas;
 import android.graphics.Matrix;
 import android.graphics.PixelFormat;
+import android.graphics.SurfaceTexture;
 import android.os.Binder;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -38,6 +39,7 @@
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.Surface;
+import android.view.TextureView;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
@@ -50,7 +52,8 @@
 /**
  * All methods must be called on UI thread
  */
-public class SendUi implements Animator.AnimatorListener, View.OnTouchListener {
+public class SendUi implements Animator.AnimatorListener, View.OnTouchListener,
+        TextureView.SurfaceTextureListener {
     private static final String LOG_TAG = "SendUI";
 
     static final float INTERMEDIATE_SCALE = 0.6f;
@@ -59,7 +62,7 @@
     static final int PRE_DURATION_MS = 300;
 
     static final float[] CLONE_SCREENSHOT_SCALE = {INTERMEDIATE_SCALE, 0.0f};
-    static final int SLOW_CLONE_DURATION_MS = 3000; // Stretch out sending over 3s
+    static final int SLOW_SEND_DURATION_MS = 3000; // Stretch out sending over 3s
     static final int FAST_CLONE_DURATION_MS = 200;
 
     static final float[] SCALE_UP_SCREENSHOT_SCALE = {INTERMEDIATE_SCALE, 1.0f};
@@ -70,6 +73,9 @@
     static final float[] TEXT_HINT_ALPHA_RANGE = {0.0f, 1.0f};
     static final int TEXT_HINT_ALPHA_DURATION_MS = 500;
 
+    static final float[] BACKGROUND_SCALE_RANGE = {1.0f, 2.0f};
+    static final int BACKGROUND_SCALE_DURATION_MS = 5000;
+
     static final int FINISH_SCALE_UP = 0;
     static final int FINISH_SLIDE_OUT = 1;
 
@@ -85,18 +91,22 @@
     final View mScreenshotLayout;
     final ImageView mScreenshotView;
     final ImageView mCloneView;
+    final ImageView mBackgroundImage;
+    final TextureView mTextureView;
     final TextView mTextHint;
     final Callback mCallback;
     final ObjectAnimator mPreAnimator;
-    final ObjectAnimator mSlowCloneAnimator;
+    final ObjectAnimator mSlowSendAnimator;
     final ObjectAnimator mFastCloneAnimator;
     final ObjectAnimator mScaleUpAnimator;
     final ObjectAnimator mHintAnimator;
     final AnimatorSet mSuccessAnimatorSet;
+    final ObjectAnimator mBackgroundAnimator;
     final boolean mHardwareAccelerated;
 
     Bitmap mScreenshotBitmap;
     ObjectAnimator mSlideoutAnimator;
+    FireflyRenderThread mFireflyRenderThread;
 
     boolean mAttached;
 
@@ -125,6 +135,10 @@
 
         mTextHint = (TextView) mScreenshotLayout.findViewById(R.id.calltoaction);
 
+        mTextureView = (TextureView) mScreenshotLayout.findViewById(R.id.fireflies);
+        mTextureView.setSurfaceTextureListener(this);
+
+        mBackgroundImage = (ImageView) mScreenshotLayout.findViewById(R.id.back);
         // We're only allowed to use hardware acceleration if
         // isHighEndGfx() returns true - otherwise, we're too limited
         // on resources to do it.
@@ -132,6 +146,11 @@
         int hwAccelerationFlags = mHardwareAccelerated ?
                 WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED : 0;
 
+        if (!mHardwareAccelerated) {
+            // Only show background in case we're not hw-accelerated
+            mBackgroundImage.setVisibility(View.VISIBLE);
+        }
+
         mWindowLayoutParams = new WindowManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                 ViewGroup.LayoutParams.MATCH_PARENT, 0, 0,
                 WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
@@ -151,9 +170,9 @@
 
         PropertyValuesHolder postX = PropertyValuesHolder.ofFloat("scaleX", CLONE_SCREENSHOT_SCALE);
         PropertyValuesHolder postY = PropertyValuesHolder.ofFloat("scaleY", CLONE_SCREENSHOT_SCALE);
-        mSlowCloneAnimator = ObjectAnimator.ofPropertyValuesHolder(mCloneView, postX, postY);
-        mSlowCloneAnimator.setInterpolator(null); // linear
-        mSlowCloneAnimator.setDuration(SLOW_CLONE_DURATION_MS);
+        mSlowSendAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, postX, postY);
+        mSlowSendAnimator.setInterpolator(null); // linear
+        mSlowSendAnimator.setDuration(SLOW_SEND_DURATION_MS);
 
         mFastCloneAnimator = ObjectAnimator.ofPropertyValuesHolder(mCloneView, postX, postY);
         mFastCloneAnimator.setInterpolator(null); // linear
@@ -174,6 +193,12 @@
         mSuccessAnimatorSet = new AnimatorSet();
         mSuccessAnimatorSet.playSequentially(mFastCloneAnimator, mScaleUpAnimator);
 
+        scaleUpX = PropertyValuesHolder.ofFloat("scaleX", BACKGROUND_SCALE_RANGE);
+        scaleUpY = PropertyValuesHolder.ofFloat("scaleY", BACKGROUND_SCALE_RANGE);
+        mBackgroundAnimator = ObjectAnimator.ofPropertyValuesHolder(mBackgroundImage, scaleUpX, scaleUpY);
+        mBackgroundAnimator.setInterpolator(new DecelerateInterpolator(2.0f));
+        mBackgroundAnimator.setDuration(BACKGROUND_SCALE_DURATION_MS);
+
         mAttached = false;
     }
 
@@ -204,7 +229,6 @@
         mCloneView.setScaleX(INTERMEDIATE_SCALE);
         mCloneView.setScaleY(INTERMEDIATE_SCALE);
 
-
         mTextHint.setVisibility(showHint ? View.VISIBLE : View.GONE);
         mTextHint.setAlpha(1.0f);
 
@@ -231,12 +255,23 @@
                 break;
         }
 
+        // Reset scale up parameters
+        PropertyValuesHolder scaleUpX = PropertyValuesHolder.ofFloat("scaleX",
+                SCALE_UP_SCREENSHOT_SCALE);
+        PropertyValuesHolder scaleUpY = PropertyValuesHolder.ofFloat("scaleY",
+                SCALE_UP_SCREENSHOT_SCALE);
+        mScaleUpAnimator.setValues(scaleUpX, scaleUpY);
+
         mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
         // Disable statusbar pull-down
         mStatusBarManager.disable(StatusBarManager.DISABLE_EXPAND);
 
         mAttached = true;
         mPreAnimator.start();
+
+        if (!mHardwareAccelerated) {
+            mBackgroundAnimator.start();
+        }
     }
 
     /** Show starting send animation */
@@ -244,11 +279,7 @@
         if (!mAttached) {
             return;
         }
-        mScreenshotView.setAlpha(0.7f);
-        mCloneView.setScaleX(INTERMEDIATE_SCALE);
-        mCloneView.setScaleY(INTERMEDIATE_SCALE);
-        mCloneView.setVisibility(View.VISIBLE);
-        mSlowCloneAnimator.start();
+        mSlowSendAnimator.start();
     }
 
     /** Show post-send animation */
@@ -257,15 +288,33 @@
             return;
         }
 
-        mSlowCloneAnimator.cancel();
+        mSlowSendAnimator.cancel();
         mTextHint.setVisibility(View.GONE);
-        // Modify the fast clone parameters to match the current scale
-        float currentScale = mCloneView.getScaleX();
-        currentScale = mCloneView.getScaleX();
 
-        PropertyValuesHolder postX = PropertyValuesHolder.ofFloat("scaleX", new float[] {currentScale, 0.0f});
-        PropertyValuesHolder postY = PropertyValuesHolder.ofFloat("scaleY", new float[] {currentScale, 0.0f});
+        float currentScale = mScreenshotView.getScaleX();
+        mScreenshotView.setAlpha(0.7f);
+
+        // Make the clone visible for scaling to the background
+        mCloneView.setScaleX(currentScale);
+        mCloneView.setScaleY(currentScale);
+        mCloneView.setVisibility(View.VISIBLE);
+
+        // Modify the fast clone parameters to match the current scale
+        PropertyValuesHolder postX = PropertyValuesHolder.ofFloat("scaleX",
+                new float[] {currentScale, 0.0f});
+        PropertyValuesHolder postY = PropertyValuesHolder.ofFloat("scaleY",
+                new float[] {currentScale, 0.0f});
         mFastCloneAnimator.setValues(postX, postY);
+        // Modify the scale up parameters to match the current scale
+        PropertyValuesHolder scaleUpX = PropertyValuesHolder.ofFloat("scaleX",
+               new float[] {currentScale, 1.0f});
+        PropertyValuesHolder scaleUpY = PropertyValuesHolder.ofFloat("scaleY",
+               new float[] {currentScale, 1.0f});
+        mScaleUpAnimator.setValues(scaleUpX, scaleUpY);
+
+        if (mFireflyRenderThread != null) {
+            mFireflyRenderThread.fadeOut();
+        }
 
         mSuccessAnimatorSet.start();
     }
@@ -294,7 +343,7 @@
             return;
         }
         mPreAnimator.cancel();
-        mSlowCloneAnimator.cancel();
+        mSlowSendAnimator.cancel();
         mFastCloneAnimator.cancel();
         mSuccessAnimatorSet.cancel();
         mScaleUpAnimator.cancel();
@@ -397,4 +446,35 @@
         mCallback.onSendConfirmed();
         return true;
     }
+
+    @Override
+    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
+        if (mHardwareAccelerated) {
+            mFireflyRenderThread = new FireflyRenderThread(mContext, surface, width, height);
+            mFireflyRenderThread.start();
+        }
+    }
+
+    @Override
+    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+        // Since we've disabled orientation changes, we can safely ignore this
+    }
+
+    @Override
+    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+        if (mFireflyRenderThread != null) {
+            mFireflyRenderThread.finish();
+            try {
+                mFireflyRenderThread.join();
+            } catch (InterruptedException e) {
+                Log.e(LOG_TAG, "Couldn't wait for FireflyRenderThread.");
+            }
+            mFireflyRenderThread = null;
+        }
+        return true;
+    }
+
+    @Override
+    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+    }
 }