diff options
author | Martijn Coenen <maco@google.com> | 2012-03-28 13:56:50 -0700 |
---|---|---|
committer | Martijn Coenen <maco@google.com> | 2012-03-28 16:35:53 -0700 |
commit | f570dca6b0fa0ce883f5c950815e43acae4f51b2 (patch) | |
tree | d3030fc50c11f6cb8d8e5cf9d7a16241eec5f6ac | |
parent | ee3d9ed14206e672e54a2db803484dacc0e0bbd4 (diff) | |
download | packages_apps_nfc-f570dca6b0fa0ce883f5c950815e43acae4f51b2.zip packages_apps_nfc-f570dca6b0fa0ce883f5c950815e43acae4f51b2.tar.gz packages_apps_nfc-f570dca6b0fa0ce883f5c950815e43acae4f51b2.tar.bz2 |
Dejank Android Beam animation.
After profiling, it turns out the Beam animation was mostly janky
because of the initial buffer allocations that take place in the
graphics driver. Modified both the animation and firefly
renderers to swap the buffers three times before drawing anything,
thereby making sure the buffers are allocated.
Do not scale the screenshot while rendering fireflies,
as that can cause jank as well.
Also modified the FireflyRenderer to keep most of its state alive
between subsequent Beams, causing it to come up faster.
Cleaned up some old code and added documentation.
Change-Id: Ica231861dd7ede178ad2ec1ab09aa00d1c876624
-rw-r--r-- | src/com/android/nfc/FireflyRenderThread.java | 394 | ||||
-rw-r--r-- | src/com/android/nfc/FireflyRenderer.java | 424 | ||||
-rw-r--r-- | src/com/android/nfc/P2pEventManager.java | 2 | ||||
-rw-r--r-- | src/com/android/nfc/SendUi.java | 248 |
4 files changed, 582 insertions, 486 deletions
diff --git a/src/com/android/nfc/FireflyRenderThread.java b/src/com/android/nfc/FireflyRenderThread.java deleted file mode 100644 index 5019ffb..0000000 --- a/src/com/android/nfc/FireflyRenderThread.java +++ /dev/null @@ -1,394 +0,0 @@ -/* - * 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 = 200; - - static final float NEAR_CLIPPING_PLANE = 50f; - static final float FAR_CLIPPING_PLANE = 100f; - - 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.glFrustumf(-mDisplayWidth, mDisplayWidth, mDisplayHeight, -mDisplayHeight, NEAR_CLIPPING_PLANE, FAR_CLIPPING_PLANE); - - // Switch back to modelview - mGL.glMatrixMode(GL10.GL_MODELVIEW); - mGL.glLoadIdentity(); - - mGL.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST); - mGL.glDepthMask(true); - - // Reset firefly models - for (Firefly firefly : mFireflies) { - firefly.reset(); - } - - mStartTime = System.currentTimeMillis(); - - 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 float TEXTURE_HEIGHT = 30f; // TODO use measurement of texture size - static final float SPEED = .5f; - - float mX; // between -mDisplayHeight and mDisplayHeight - float mY; // between -mDisplayWidth and mDisplayWidth - float mZ; // between 0.0 (near) and 1.0 (far) - float mZ0; - float mT; - float mScale; - float mAlpha; - - public Firefly() { - reset(); - } - - void reset() { - mX = (float) (Math.random() * mDisplayWidth) * 4 - 2 * mDisplayWidth; - mY = (float) (Math.random() * mDisplayHeight) * 4 - 2 * mDisplayHeight; - mZ0 = mZ = (float) (Math.random()) * 2 - 1; - mT = 0f; - mScale = 1.5f; - mAlpha = 0f; - } - - public void updatePositionAndScale(long timeElapsedMs) { - mT += timeElapsedMs; - mZ = mZ0 + mT/1000f * SPEED; - mAlpha = 1f-mZ; - if(mZ > 1.0) reset(); - } - - 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, -NEAR_CLIPPING_PLANE-mZ*(FAR_CLIPPING_PLANE-NEAR_CLIPPING_PLANE)); - mGL.glColor4f(1, 1, 1, mAlpha); - - // scale around center - mGL.glTranslatef(TEXTURE_HEIGHT/2, TEXTURE_HEIGHT/2, 0); - mGL.glScalef(mScale, mScale, 0); - mGL.glTranslatef(-TEXTURE_HEIGHT/2, -TEXTURE_HEIGHT/2, 0); - - 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/FireflyRenderer.java b/src/com/android/nfc/FireflyRenderer.java new file mode 100644 index 0000000..4ce58b4 --- /dev/null +++ b/src/com/android/nfc/FireflyRenderer.java @@ -0,0 +1,424 @@ +/* + * 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 FireflyRenderer { + private static final String LOG_TAG = "NfcFireflyThread"; + + static final int NUM_FIREFLIES = 200; + + static final float NEAR_CLIPPING_PLANE = 50f; + static final float FAR_CLIPPING_PLANE = 100f; + + // All final variables below only need to be allocated once + // and can be reused between subsequent Beams + 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 }; + + final Context mContext; + + // Buffer holding the vertices + final FloatBuffer mVertexBuffer; + + // Buffer holding the indices + final ShortBuffer mIndexBuffer; + + // Buffer holding the texture mapping coordinates + final FloatBuffer mTextureBuffer; + + final Firefly[] mFireflies; + + FireflyRenderThread mFireflyRenderThread; + + // The surface to render the flies on, including width and height + SurfaceTexture mSurface; + int mDisplayWidth; + int mDisplayHeight; + + public FireflyRenderer(Context context) { + mContext = context; + + // 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); + + mFireflies = new Firefly[NUM_FIREFLIES]; + for (int i = 0; i < NUM_FIREFLIES; i++) { + mFireflies[i] = new Firefly(); + } + } + + /** + * Starts rendering fireflies on the given surface. + * Must be called from the UI-thread. + */ + public void start(SurfaceTexture surface, int width, int height) { + mSurface = surface; + mDisplayWidth = width; + mDisplayHeight = height; + + mFireflyRenderThread = new FireflyRenderThread(); + mFireflyRenderThread.start(); + } + + /** + * Stops rendering fireflies. + * Must be called from the UI-thread. + */ + public void stop() { + if (mFireflyRenderThread != null) { + mFireflyRenderThread.finish(); + try { + mFireflyRenderThread.join(); + } catch (InterruptedException e) { + Log.e(LOG_TAG, "Couldn't wait for FireflyRenderThread."); + } + mFireflyRenderThread = null; + } + } + + private class FireflyRenderThread extends Thread { + EGL10 mEgl; + EGLDisplay mEglDisplay; + EGLConfig mEglConfig; + EGLContext mEglContext; + EGLSurface mEglSurface; + GL10 mGL; + + // Holding the handle to the texture + int mTextureId; + + // Read/written by multiple threads + volatile boolean mFinished; + + @Override + public void run() { + if (!initGL()) { + Log.e(LOG_TAG, "Failed to initialize OpenGL."); + return; + } + loadStarTexture(); + + mGL.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + + mGL.glViewport(0, 0, mDisplayWidth, mDisplayHeight); + + // make adjustments for screen ratio + mGL.glMatrixMode(GL10.GL_PROJECTION); + mGL.glLoadIdentity(); + mGL.glFrustumf(-mDisplayWidth, mDisplayWidth, mDisplayHeight, -mDisplayHeight, NEAR_CLIPPING_PLANE, FAR_CLIPPING_PLANE); + + // Switch back to modelview + mGL.glMatrixMode(GL10.GL_MODELVIEW); + mGL.glLoadIdentity(); + + mGL.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST); + mGL.glDepthMask(true); + + + for (Firefly firefly : mFireflies) { + firefly.reset(); + } + + for (int i = 0; i < 3; i++) { + // Call eglSwapBuffers 3 times - this will allocate the necessary + // buffers, and make sure the animation looks smooth from the start. + if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) { + Log.e(LOG_TAG, "Could not swap buffers"); + mFinished = true; + } + } + + long startTime = System.currentTimeMillis(); + + while (!mFinished) { + long timeElapsedMs = System.currentTimeMillis() - startTime; + startTime = 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(mGL); + } + + if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) { + Log.e(LOG_TAG, "Could not swap buffers"); + mFinished = true; + } + + long elapsed = System.currentTimeMillis() - startTime; + try { + Thread.sleep(Math.max(30 - elapsed, 0)); + } catch (InterruptedException e) { + + } + } + finishGL(); + } + + public void finish() { + mFinished = true; + } + + 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) { } + } + } + } + + 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())); + } + } + } + + 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 " + Integer.toString(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 (mEglSurface != null) { + mEgl.eglDestroySurface(mEglDisplay, mEglSurface); + } + + if (mEglContext != null) { + mEgl.eglDestroyContext(mEglDisplay, mEglContext); + } + } + + 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; + } + } + + private class Firefly { + static final float TEXTURE_HEIGHT = 30f; // TODO use measurement of texture size + static final float SPEED = .5f; + + float mX; // between -mDisplayHeight and mDisplayHeight + float mY; // between -mDisplayWidth and mDisplayWidth + float mZ; // between 0.0 (near) and 1.0 (far) + float mZ0; + float mT; + float mScale; + float mAlpha; + + public Firefly() { + } + + void reset() { + mX = (float) (Math.random() * mDisplayWidth) * 4 - 2 * mDisplayWidth; + mY = (float) (Math.random() * mDisplayHeight) * 4 - 2 * mDisplayHeight; + mZ0 = mZ = (float) (Math.random()) * 2 - 1; + mT = 0f; + mScale = 1.5f; + mAlpha = 0f; + } + + public void updatePositionAndScale(long timeElapsedMs) { + mT += timeElapsedMs; + mZ = mZ0 + mT/1000f * SPEED; + mAlpha = 1f-mZ; + if(mZ > 1.0) reset(); + } + + public void draw(GL10 gl) { + gl.glLoadIdentity(); + + // Counter clockwise winding + gl.glFrontFace(GL10.GL_CCW); + + gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); + gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); + + gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mVertexBuffer); + gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, mTextureBuffer); + + gl.glTranslatef(mX, mY, -NEAR_CLIPPING_PLANE-mZ*(FAR_CLIPPING_PLANE-NEAR_CLIPPING_PLANE)); + gl.glColor4f(1, 1, 1, mAlpha); + + // scale around center + gl.glTranslatef(TEXTURE_HEIGHT/2, TEXTURE_HEIGHT/2, 0); + gl.glScalef(mScale, mScale, 0); + gl.glTranslatef(-TEXTURE_HEIGHT/2, -TEXTURE_HEIGHT/2, 0); + + gl.glDrawElements(GL10.GL_TRIANGLES, mIndices.length, GL10.GL_UNSIGNED_SHORT, + mIndexBuffer); + + gl.glColor4f(1, 1, 1, 1); + gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY); + gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); + } + } +} diff --git a/src/com/android/nfc/P2pEventManager.java b/src/com/android/nfc/P2pEventManager.java index fdeede5..4a57deb 100644 --- a/src/com/android/nfc/P2pEventManager.java +++ b/src/com/android/nfc/P2pEventManager.java @@ -81,7 +81,7 @@ public class P2pEventManager implements P2pEventListener, SendUi.Callback { public void onP2pSendComplete() { mNfcService.playSound(NfcService.SOUND_END); mVibrator.vibrate(VIBRATION_PATTERN, -1); - mSendUi.showPostSend(); + mSendUi.finish(SendUi.FINISH_SEND_SUCCESS); mSending = false; mNdefSent = true; } diff --git a/src/com/android/nfc/SendUi.java b/src/com/android/nfc/SendUi.java index 6fc24d5..ae54e0a 100644 --- a/src/com/android/nfc/SendUi.java +++ b/src/com/android/nfc/SendUi.java @@ -20,6 +20,7 @@ import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; +import android.animation.TimeAnimator; import android.app.ActivityManager; import android.app.StatusBarManager; import android.content.Context; @@ -32,7 +33,6 @@ import android.graphics.PixelFormat; import android.graphics.SurfaceTexture; import android.os.Binder; import android.util.DisplayMetrics; -import android.util.Log; import android.view.Display; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -42,26 +42,43 @@ import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.view.animation.AccelerateDecelerateInterpolator; -import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import android.widget.ImageView; import android.widget.TextView; /** - * All methods must be called on UI thread + * This class is responsible for handling the UI animation + * around Android Beam. The animation consists of the following + * animators: + * + * mPreAnimator: scales the screenshot down to INTERMEDIATE_SCALE + * mSlowSendAnimator: scales the screenshot down to 0.2f (used as a "send in progress" animation) + * mFastSendAnimator: quickly scales the screenshot down to 0.0f (used for send success) + * mFadeInAnimator: fades the current activity back in (used after mFastSendAnimator completes) + * mScaleUpAnimator: scales the screenshot back up to full screen (used for failure or receiving) + * mHintAnimator: Slowly turns up the alpha of the "Touch to Beam" hint + * + * Possible sequences are: + * + * mPreAnimator => mSlowSendAnimator => mFastSendAnimator => mFadeInAnimator (send success) + * mPreAnimator => mSlowSendAnimator => mScaleUpAnimator (send failure) + * mPreAnimator => mScaleUpAnimator (p2p link broken, or data received) + * + * Note that mFastSendAnimator and mFadeInAnimator are combined in a set, as they + * are an atomic animation that cannot be interrupted. + * + * All methods of this class must be called on the UI thread */ public class SendUi implements Animator.AnimatorListener, View.OnTouchListener, - TextureView.SurfaceTextureListener { - private static final String LOG_TAG = "SendUI"; - + TimeAnimator.TimeListener, TextureView.SurfaceTextureListener { static final float INTERMEDIATE_SCALE = 0.6f; static final float[] PRE_SCREENSHOT_SCALE = {1.0f, INTERMEDIATE_SCALE}; static final int PRE_DURATION_MS = 350; - static final float[] CLONE_SCREENSHOT_SCALE = {INTERMEDIATE_SCALE, 0.2f}; + static final float[] SEND_SCREENSHOT_SCALE = {INTERMEDIATE_SCALE, 0.2f}; static final int SLOW_SEND_DURATION_MS = 8000; // Stretch out sending over 8s - static final int FAST_CLONE_DURATION_MS = 350; + static final int FAST_SEND_DURATION_MS = 350; static final float[] SCALE_UP_SCREENSHOT_SCALE = {INTERMEDIATE_SCALE, 1.0f}; static final int SCALE_UP_DURATION_MS = 300; @@ -76,7 +93,7 @@ public class SendUi implements Animator.AnimatorListener, View.OnTouchListener, static final int TEXT_HINT_ALPHA_START_DELAY_MS = 300; static final int FINISH_SCALE_UP = 0; - static final int FINISH_SLIDE_OUT = 1; + static final int FINISH_SEND_SUCCESS = 1; // all members are only used on UI thread final WindowManager mWindowManager; @@ -92,20 +109,47 @@ public class SendUi implements Animator.AnimatorListener, View.OnTouchListener, final TextureView mTextureView; final TextView mTextHint; final Callback mCallback; + + // The mFrameCounter animation is purely used to count down a certain + // number of (vsync'd) frames. This is needed because the first 3 + // times the animation internally calls eglSwapBuffers(), large buffers + // are allocated by the graphics drivers. This causes the animation + // to look janky. So on platforms where we can use hardware acceleration, + // the animation order is: + // Wait for hw surface => start frame counter => start pre-animation after 3 frames + // For platforms where no hw acceleration can be used, the pre-animation + // is started immediately. + final TimeAnimator mFrameCounterAnimator; + final ObjectAnimator mPreAnimator; final ObjectAnimator mSlowSendAnimator; - final ObjectAnimator mFastCloneAnimator; + final ObjectAnimator mFastSendAnimator; final ObjectAnimator mFadeInAnimator; final ObjectAnimator mHintAnimator; + final ObjectAnimator mScaleUpAnimator; final AnimatorSet mSuccessAnimatorSet; + + // Besides animating the screenshot, the Beam UI also renders + // fireflies on platforms where we can do hardware-acceleration. + // Firefly rendering is only started once the initial + // "pre-animation" has scaled down the screenshot, to avoid + // that animation becoming janky. Likewise, the fireflies are + // stopped in their tracks as soon as we finish the animation, + // to make the finishing animation smooth. final boolean mHardwareAccelerated; + final FireflyRenderer mFireflyRenderer; Bitmap mScreenshotBitmap; - ObjectAnimator mSlideoutAnimator; - ObjectAnimator mScaleUpAnimator; - FireflyRenderThread mFireflyRenderThread; boolean mAttached; + boolean mSending; + + int mRenderedFrames; + + // Used for holding the surface + SurfaceTexture mSurface; + int mSurfaceWidth; + int mSurfaceHeight; interface Callback { public void onSendConfirmed(); @@ -125,6 +169,7 @@ public class SendUi implements Animator.AnimatorListener, View.OnTouchListener, mLayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mScreenshotLayout = mLayoutInflater.inflate(R.layout.screenshot, null); + mScreenshotView = (ImageView) mScreenshotLayout.findViewById(R.id.screenshot); mScreenshotLayout.setFocusable(true); @@ -150,6 +195,9 @@ public class SendUi implements Animator.AnimatorListener, View.OnTouchListener, PixelFormat.OPAQUE); mWindowLayoutParams.token = new Binder(); + mFrameCounterAnimator = new TimeAnimator(); + mFrameCounterAnimator.setTimeListener(this); + PropertyValuesHolder preX = PropertyValuesHolder.ofFloat("scaleX", PRE_SCREENSHOT_SCALE); PropertyValuesHolder preY = PropertyValuesHolder.ofFloat("scaleY", PRE_SCREENSHOT_SCALE); mPreAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, preX, preY); @@ -157,8 +205,8 @@ public class SendUi implements Animator.AnimatorListener, View.OnTouchListener, mPreAnimator.setDuration(PRE_DURATION_MS); mPreAnimator.addListener(this); - PropertyValuesHolder postX = PropertyValuesHolder.ofFloat("scaleX", CLONE_SCREENSHOT_SCALE); - PropertyValuesHolder postY = PropertyValuesHolder.ofFloat("scaleY", CLONE_SCREENSHOT_SCALE); + PropertyValuesHolder postX = PropertyValuesHolder.ofFloat("scaleX", SEND_SCREENSHOT_SCALE); + PropertyValuesHolder postY = PropertyValuesHolder.ofFloat("scaleY", SEND_SCREENSHOT_SCALE); PropertyValuesHolder alphaDown = PropertyValuesHolder.ofFloat("alpha", new float[]{1.0f, 0.0f}); @@ -166,11 +214,11 @@ public class SendUi implements Animator.AnimatorListener, View.OnTouchListener, mSlowSendAnimator.setInterpolator(new DecelerateInterpolator()); mSlowSendAnimator.setDuration(SLOW_SEND_DURATION_MS); - mFastCloneAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, postX, + mFastSendAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, postX, postY, alphaDown); - mFastCloneAnimator.setInterpolator(new DecelerateInterpolator()); - mFastCloneAnimator.setDuration(FAST_CLONE_DURATION_MS); - mFastCloneAnimator.addListener(this); + mFastSendAnimator.setInterpolator(new DecelerateInterpolator()); + mFastSendAnimator.setDuration(FAST_SEND_DURATION_MS); + mFastSendAnimator.addListener(this); PropertyValuesHolder scaleUpX = PropertyValuesHolder.ofFloat("scaleX", SCALE_UP_SCREENSHOT_SCALE); PropertyValuesHolder scaleUpY = PropertyValuesHolder.ofFloat("scaleY", SCALE_UP_SCREENSHOT_SCALE); @@ -194,8 +242,13 @@ public class SendUi implements Animator.AnimatorListener, View.OnTouchListener, mHintAnimator.setStartDelay(TEXT_HINT_ALPHA_START_DELAY_MS); mSuccessAnimatorSet = new AnimatorSet(); - mSuccessAnimatorSet.playSequentially(mFastCloneAnimator, mFadeInAnimator); + mSuccessAnimatorSet.playSequentially(mFastSendAnimator, mFadeInAnimator); + if (mHardwareAccelerated) { + mFireflyRenderer = new FireflyRenderer(context); + } else { + mFireflyRenderer = null; + } mAttached = false; } @@ -253,92 +306,85 @@ public class SendUi implements Animator.AnimatorListener, View.OnTouchListener, // Disable statusbar pull-down mStatusBarManager.disable(StatusBarManager.DISABLE_EXPAND); + mSending = false; mAttached = true; - mPreAnimator.start(); + + if (!mHardwareAccelerated) { + mPreAnimator.start(); + } // else, we will start the animation once we get the hardware surface } /** Show starting send animation */ public void showStartSend() { - if (!mAttached) { - return; - } - mSlowSendAnimator.start(); - } - - /** Show post-send animation */ - public void showPostSend() { - if (!mAttached) { - return; - } - - mSlowSendAnimator.cancel(); - mTextHint.setVisibility(View.GONE); - + if (!mAttached) return; + // Update the starting scale - touchscreen-mashers may trigger + // this before the pre-animation completes. float currentScale = mScreenshotView.getScaleX(); - - // 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}); - PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", - new float[] {1.0f, 0.0f}); - mFastCloneAnimator.setValues(postX, postY, alpha); - - // Modify the fadeIn parameters to match the current scale - PropertyValuesHolder fadeIn = PropertyValuesHolder.ofFloat("alpha", - new float[] {0.0f, 1.0f}); - mFadeInAnimator.setValues(fadeIn); - if (mFireflyRenderThread != null) { - mFireflyRenderThread.fadeOut(); - } - - mSuccessAnimatorSet.start(); + mSlowSendAnimator.setValues(postX, postY); + mSlowSendAnimator.start(); } /** Return to initial state */ public void finish(int finishMode) { - if (!mAttached) { - return; + if (!mAttached) return; + + // Stop rendering the fireflies + if (mFireflyRenderer != null) { + mFireflyRenderer.stop(); } + mTextHint.setVisibility(View.GONE); - if (finishMode == FINISH_SLIDE_OUT) { - PropertyValuesHolder slideX = PropertyValuesHolder.ofFloat("translationX", - new float[]{0.0f, mScreenshotView.getWidth()}); - mSlideoutAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, slideX); - mSlideoutAnimator.setInterpolator(new AccelerateInterpolator()); - mSlideoutAnimator.setDuration(SLIDE_OUT_DURATION_MS); - mSlideoutAnimator.addListener(this); - mSlideoutAnimator.start(); - } else { - float currentScale = mScreenshotView.getScaleX(); - float currentAlpha = mScreenshotView.getAlpha(); + + float currentScale = mScreenshotView.getScaleX(); + float currentAlpha = mScreenshotView.getAlpha(); + + if (finishMode == FINISH_SCALE_UP) { PropertyValuesHolder scaleUpX = PropertyValuesHolder.ofFloat("scaleX", new float[] {currentScale, 1.0f}); PropertyValuesHolder scaleUpY = PropertyValuesHolder.ofFloat("scaleY", new float[] {currentScale, 1.0f}); PropertyValuesHolder scaleUpAlpha = PropertyValuesHolder.ofFloat("alpha", new float[] {currentAlpha, 1.0f}); - mScaleUpAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, scaleUpX, scaleUpY, scaleUpAlpha); - mScaleUpAnimator.setInterpolator(new DecelerateInterpolator()); - mScaleUpAnimator.setDuration(SCALE_UP_DURATION_MS); - mScaleUpAnimator.addListener(this); + mScaleUpAnimator.setValues(scaleUpX, scaleUpY, scaleUpAlpha); + mScaleUpAnimator.start(); + } else if (finishMode == FINISH_SEND_SUCCESS){ + // Modify the fast send 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}); + PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", + new float[] {1.0f, 0.0f}); + mFastSendAnimator.setValues(postX, postY, alpha); + + // Reset the fadeIn parameters to start from alpha 1 + PropertyValuesHolder fadeIn = PropertyValuesHolder.ofFloat("alpha", + new float[] {0.0f, 1.0f}); + mFadeInAnimator.setValues(fadeIn); + + mSlowSendAnimator.cancel(); + mSuccessAnimatorSet.start(); } } public void dismiss() { - if (!mAttached) { - return; - } + if (!mAttached) return; + // Immediately set to false, to prevent .cancel() calls // below from immediately calling into dismiss() again. mAttached = false; + mSurface = null; + mFrameCounterAnimator.cancel(); mPreAnimator.cancel(); mSlowSendAnimator.cancel(); - mFastCloneAnimator.cancel(); + mFastSendAnimator.cancel(); mSuccessAnimatorSet.cancel(); mScaleUpAnimator.cancel(); mWindowManager.removeView(mScreenshotLayout); @@ -403,7 +449,6 @@ public class SendUi implements Animator.AnimatorListener, View.OnTouchListener, return null; } - if (requiresRotation) { // Rotate the screenshot to the current orientation Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels, @@ -444,13 +489,18 @@ public class SendUi implements Animator.AnimatorListener, View.OnTouchListener, @Override public void onAnimationEnd(Animator animation) { if (animation == mScaleUpAnimator || animation == mSuccessAnimatorSet || - animation == mSlideoutAnimator || animation == mFadeInAnimator) { + animation == mFadeInAnimator) { + // These all indicate the end of the animation dismiss(); - } else if (animation == mFastCloneAnimator) { - // After cloning is done and we've faded out, reset the scale to 1 + } else if (animation == mFastSendAnimator) { + // After sending is done and we've faded out, reset the scale to 1 // so we can fade it back in. mScreenshotView.setScaleX(1.0f); mScreenshotView.setScaleY(1.0f); + } else if (animation == mPreAnimator) { + if (mHardwareAccelerated && mAttached && !mSending) { + mFireflyRenderer.start(mSurface, mSurfaceWidth, mSurfaceHeight); + } } } @@ -461,23 +511,46 @@ public class SendUi implements Animator.AnimatorListener, View.OnTouchListener, public void onAnimationRepeat(Animator animation) { } @Override + public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) { + // This gets called on animation vsync + if (++mRenderedFrames < 4) { + // For the first 3 frames, call invalidate(); this calls eglSwapBuffers + // on the surface, which will allocate large buffers the first three calls + // as Android uses triple buffering. + mScreenshotLayout.invalidate(); + } else { + // Buffers should be allocated, start the real animation + mFrameCounterAnimator.cancel(); + mPreAnimator.start(); + } + } + + @Override public boolean onTouch(View v, MotionEvent event) { if (!mAttached) { return false; } + mSending = true; // Ignore future touches mScreenshotView.setOnTouchListener(null); - mPreAnimator.end(); + // Cancel any ongoing animations + mFrameCounterAnimator.cancel(); + mPreAnimator.cancel(); + 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(); + if (mHardwareAccelerated && !mSending) { + mRenderedFrames = 0; + + mFrameCounterAnimator.start(); + mSurface = surface; + mSurfaceWidth = width; + mSurfaceHeight = height; } } @@ -488,19 +561,12 @@ public class SendUi implements Animator.AnimatorListener, View.OnTouchListener, @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; - } + mSurface = null; + return true; } @Override - public void onSurfaceTextureUpdated(SurfaceTexture surface) { - } + public void onSurfaceTextureUpdated(SurfaceTexture surface) { } + } |