Home | History | Annotate | Download | only in nfc
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.nfc;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorSet;
     21 import android.animation.ObjectAnimator;
     22 import android.animation.PropertyValuesHolder;
     23 import android.animation.TimeAnimator;
     24 import android.app.ActivityManager;
     25 import android.app.StatusBarManager;
     26 import android.content.Context;
     27 import android.content.pm.ActivityInfo;
     28 import android.content.res.Configuration;
     29 import android.graphics.Bitmap;
     30 import android.graphics.Canvas;
     31 import android.graphics.Matrix;
     32 import android.graphics.PixelFormat;
     33 import android.graphics.SurfaceTexture;
     34 import android.os.Binder;
     35 import android.util.DisplayMetrics;
     36 import android.view.Display;
     37 import android.view.LayoutInflater;
     38 import android.view.MotionEvent;
     39 import android.view.Surface;
     40 import android.view.TextureView;
     41 import android.view.View;
     42 import android.view.ViewGroup;
     43 import android.view.WindowManager;
     44 import android.view.animation.AccelerateDecelerateInterpolator;
     45 import android.view.animation.DecelerateInterpolator;
     46 import android.widget.ImageView;
     47 import android.widget.TextView;
     48 import android.widget.Toast;
     49 
     50 /**
     51  * This class is responsible for handling the UI animation
     52  * around Android Beam. The animation consists of the following
     53  * animators:
     54  *
     55  * mPreAnimator: scales the screenshot down to INTERMEDIATE_SCALE
     56  * mSlowSendAnimator: scales the screenshot down to 0.2f (used as a "send in progress" animation)
     57  * mFastSendAnimator: quickly scales the screenshot down to 0.0f (used for send success)
     58  * mFadeInAnimator: fades the current activity back in (used after mFastSendAnimator completes)
     59  * mScaleUpAnimator: scales the screenshot back up to full screen (used for failure or receiving)
     60  * mHintAnimator: Slowly turns up the alpha of the "Touch to Beam" hint
     61  *
     62  * Possible sequences are:
     63  *
     64  * mPreAnimator => mSlowSendAnimator => mFastSendAnimator => mFadeInAnimator (send success)
     65  * mPreAnimator => mSlowSendAnimator => mScaleUpAnimator (send failure)
     66  * mPreAnimator => mScaleUpAnimator (p2p link broken, or data received)
     67  *
     68  * Note that mFastSendAnimator and mFadeInAnimator are combined in a set, as they
     69  * are an atomic animation that cannot be interrupted.
     70  *
     71  * All methods of this class must be called on the UI thread
     72  */
     73 public class SendUi implements Animator.AnimatorListener, View.OnTouchListener,
     74         TimeAnimator.TimeListener, TextureView.SurfaceTextureListener {
     75     static final float INTERMEDIATE_SCALE = 0.6f;
     76 
     77     static final float[] PRE_SCREENSHOT_SCALE = {1.0f, INTERMEDIATE_SCALE};
     78     static final int PRE_DURATION_MS = 350;
     79 
     80     static final float[] SEND_SCREENSHOT_SCALE = {INTERMEDIATE_SCALE, 0.2f};
     81     static final int SLOW_SEND_DURATION_MS = 8000; // Stretch out sending over 8s
     82     static final int FAST_SEND_DURATION_MS = 350;
     83 
     84     static final float[] SCALE_UP_SCREENSHOT_SCALE = {INTERMEDIATE_SCALE, 1.0f};
     85     static final int SCALE_UP_DURATION_MS = 300;
     86 
     87     static final int FADE_IN_DURATION_MS = 250;
     88     static final int FADE_IN_START_DELAY_MS = 350;
     89 
     90     static final int SLIDE_OUT_DURATION_MS = 300;
     91 
     92     static final float[] TEXT_HINT_ALPHA_RANGE = {0.0f, 1.0f};
     93     static final int TEXT_HINT_ALPHA_DURATION_MS = 500;
     94     static final int TEXT_HINT_ALPHA_START_DELAY_MS = 300;
     95 
     96     static final int FINISH_SCALE_UP = 0;
     97     static final int FINISH_SEND_SUCCESS = 1;
     98 
     99     // all members are only used on UI thread
    100     final WindowManager mWindowManager;
    101     final Context mContext;
    102     final Display mDisplay;
    103     final DisplayMetrics mDisplayMetrics;
    104     final Matrix mDisplayMatrix;
    105     final WindowManager.LayoutParams mWindowLayoutParams;
    106     final LayoutInflater mLayoutInflater;
    107     final StatusBarManager mStatusBarManager;
    108     final View mScreenshotLayout;
    109     final ImageView mScreenshotView;
    110     final TextureView mTextureView;
    111     final TextView mTextHint;
    112     final Callback mCallback;
    113 
    114     // The mFrameCounter animation is purely used to count down a certain
    115     // number of (vsync'd) frames. This is needed because the first 3
    116     // times the animation internally calls eglSwapBuffers(), large buffers
    117     // are allocated by the graphics drivers. This causes the animation
    118     // to look janky. So on platforms where we can use hardware acceleration,
    119     // the animation order is:
    120     // Wait for hw surface => start frame counter => start pre-animation after 3 frames
    121     // For platforms where no hw acceleration can be used, the pre-animation
    122     // is started immediately.
    123     final TimeAnimator mFrameCounterAnimator;
    124 
    125     final ObjectAnimator mPreAnimator;
    126     final ObjectAnimator mSlowSendAnimator;
    127     final ObjectAnimator mFastSendAnimator;
    128     final ObjectAnimator mFadeInAnimator;
    129     final ObjectAnimator mHintAnimator;
    130     final ObjectAnimator mScaleUpAnimator;
    131     final AnimatorSet mSuccessAnimatorSet;
    132 
    133     // Besides animating the screenshot, the Beam UI also renders
    134     // fireflies on platforms where we can do hardware-acceleration.
    135     // Firefly rendering is only started once the initial
    136     // "pre-animation" has scaled down the screenshot, to avoid
    137     // that animation becoming janky. Likewise, the fireflies are
    138     // stopped in their tracks as soon as we finish the animation,
    139     // to make the finishing animation smooth.
    140     final boolean mHardwareAccelerated;
    141     final FireflyRenderer mFireflyRenderer;
    142 
    143     String mToastString;
    144     Bitmap mScreenshotBitmap;
    145 
    146     boolean mAttached;
    147     boolean mSending;
    148 
    149     int mRenderedFrames;
    150 
    151     // Used for holding the surface
    152     SurfaceTexture mSurface;
    153     int mSurfaceWidth;
    154     int mSurfaceHeight;
    155 
    156     interface Callback {
    157         public void onSendConfirmed();
    158     }
    159 
    160     public SendUi(Context context, Callback callback) {
    161         mContext = context;
    162         mCallback = callback;
    163 
    164         mDisplayMetrics = new DisplayMetrics();
    165         mDisplayMatrix = new Matrix();
    166         mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    167         mStatusBarManager = (StatusBarManager) context.getSystemService(Context.STATUS_BAR_SERVICE);
    168 
    169         mDisplay = mWindowManager.getDefaultDisplay();
    170 
    171         mLayoutInflater = (LayoutInflater)
    172                 context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    173         mScreenshotLayout = mLayoutInflater.inflate(R.layout.screenshot, null);
    174 
    175         mScreenshotView = (ImageView) mScreenshotLayout.findViewById(R.id.screenshot);
    176         mScreenshotLayout.setFocusable(true);
    177 
    178         mTextHint = (TextView) mScreenshotLayout.findViewById(R.id.calltoaction);
    179 
    180         mTextureView = (TextureView) mScreenshotLayout.findViewById(R.id.fireflies);
    181         mTextureView.setSurfaceTextureListener(this);
    182 
    183         // We're only allowed to use hardware acceleration if
    184         // isHighEndGfx() returns true - otherwise, we're too limited
    185         // on resources to do it.
    186         mHardwareAccelerated = ActivityManager.isHighEndGfx();
    187         int hwAccelerationFlags = mHardwareAccelerated ?
    188                 WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED : 0;
    189 
    190         mWindowLayoutParams = new WindowManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
    191                 ViewGroup.LayoutParams.MATCH_PARENT, 0, 0,
    192                 WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
    193                 WindowManager.LayoutParams.FLAG_FULLSCREEN
    194                 | hwAccelerationFlags
    195                 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
    196                 PixelFormat.OPAQUE);
    197         mWindowLayoutParams.privateFlags |=
    198                 WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
    199         mWindowLayoutParams.token = new Binder();
    200 
    201         mFrameCounterAnimator = new TimeAnimator();
    202         mFrameCounterAnimator.setTimeListener(this);
    203 
    204         PropertyValuesHolder preX = PropertyValuesHolder.ofFloat("scaleX", PRE_SCREENSHOT_SCALE);
    205         PropertyValuesHolder preY = PropertyValuesHolder.ofFloat("scaleY", PRE_SCREENSHOT_SCALE);
    206         mPreAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, preX, preY);
    207         mPreAnimator.setInterpolator(new DecelerateInterpolator());
    208         mPreAnimator.setDuration(PRE_DURATION_MS);
    209         mPreAnimator.addListener(this);
    210 
    211         PropertyValuesHolder postX = PropertyValuesHolder.ofFloat("scaleX", SEND_SCREENSHOT_SCALE);
    212         PropertyValuesHolder postY = PropertyValuesHolder.ofFloat("scaleY", SEND_SCREENSHOT_SCALE);
    213         PropertyValuesHolder alphaDown = PropertyValuesHolder.ofFloat("alpha",
    214                 new float[]{1.0f, 0.0f});
    215 
    216         mSlowSendAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, postX, postY);
    217         mSlowSendAnimator.setInterpolator(new DecelerateInterpolator());
    218         mSlowSendAnimator.setDuration(SLOW_SEND_DURATION_MS);
    219 
    220         mFastSendAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, postX,
    221                 postY, alphaDown);
    222         mFastSendAnimator.setInterpolator(new DecelerateInterpolator());
    223         mFastSendAnimator.setDuration(FAST_SEND_DURATION_MS);
    224         mFastSendAnimator.addListener(this);
    225 
    226         PropertyValuesHolder scaleUpX = PropertyValuesHolder.ofFloat("scaleX", SCALE_UP_SCREENSHOT_SCALE);
    227         PropertyValuesHolder scaleUpY = PropertyValuesHolder.ofFloat("scaleY", SCALE_UP_SCREENSHOT_SCALE);
    228 
    229         mScaleUpAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, scaleUpX, scaleUpY);
    230         mScaleUpAnimator.setInterpolator(new DecelerateInterpolator());
    231         mScaleUpAnimator.setDuration(SCALE_UP_DURATION_MS);
    232         mScaleUpAnimator.addListener(this);
    233 
    234         PropertyValuesHolder fadeIn = PropertyValuesHolder.ofFloat("alpha", 1.0f);
    235         mFadeInAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, fadeIn);
    236         mFadeInAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
    237         mFadeInAnimator.setDuration(FADE_IN_DURATION_MS);
    238         mFadeInAnimator.setStartDelay(FADE_IN_START_DELAY_MS);
    239         mFadeInAnimator.addListener(this);
    240 
    241         PropertyValuesHolder alphaUp = PropertyValuesHolder.ofFloat("alpha", TEXT_HINT_ALPHA_RANGE);
    242         mHintAnimator = ObjectAnimator.ofPropertyValuesHolder(mTextHint, alphaUp);
    243         mHintAnimator.setInterpolator(null);
    244         mHintAnimator.setDuration(TEXT_HINT_ALPHA_DURATION_MS);
    245         mHintAnimator.setStartDelay(TEXT_HINT_ALPHA_START_DELAY_MS);
    246 
    247         mSuccessAnimatorSet = new AnimatorSet();
    248         mSuccessAnimatorSet.playSequentially(mFastSendAnimator, mFadeInAnimator);
    249 
    250         if (mHardwareAccelerated) {
    251             mFireflyRenderer = new FireflyRenderer(context);
    252         } else {
    253             mFireflyRenderer = null;
    254         }
    255         mAttached = false;
    256     }
    257 
    258     public void takeScreenshot() {
    259         mScreenshotBitmap = createScreenshot();
    260     }
    261 
    262     /** Show pre-send animation */
    263     public void showPreSend() {
    264         // Update display metrics
    265         mDisplay.getRealMetrics(mDisplayMetrics);
    266 
    267         final int statusBarHeight = mContext.getResources().getDimensionPixelSize(
    268                                         com.android.internal.R.dimen.status_bar_height);
    269 
    270         if (mScreenshotBitmap == null || mAttached) {
    271             return;
    272         }
    273         mScreenshotView.setOnTouchListener(this);
    274         mScreenshotView.setImageBitmap(mScreenshotBitmap);
    275         mScreenshotView.setTranslationX(0f);
    276         mScreenshotView.setAlpha(1.0f);
    277         mScreenshotView.setPadding(0, statusBarHeight, 0, 0);
    278 
    279         mScreenshotLayout.requestFocus();
    280 
    281         mTextHint.setAlpha(0.0f);
    282         mTextHint.setVisibility(View.VISIBLE);
    283         mHintAnimator.start();
    284 
    285         // Lock the orientation.
    286         // The orientation from the configuration does not specify whether
    287         // the orientation is reverse or not (ie landscape or reverse landscape).
    288         // So we have to use SENSOR_LANDSCAPE or SENSOR_PORTRAIT to make sure
    289         // we lock in portrait / landscape and have the sensor determine
    290         // which way is up.
    291         int orientation = mContext.getResources().getConfiguration().orientation;
    292 
    293         switch (orientation) {
    294             case Configuration.ORIENTATION_LANDSCAPE:
    295                 mWindowLayoutParams.screenOrientation =
    296                         ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
    297                 break;
    298             case Configuration.ORIENTATION_PORTRAIT:
    299                 mWindowLayoutParams.screenOrientation =
    300                         ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;
    301                 break;
    302             default:
    303                 mWindowLayoutParams.screenOrientation =
    304                         ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;
    305                 break;
    306         }
    307 
    308         mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
    309         // Disable statusbar pull-down
    310         mStatusBarManager.disable(StatusBarManager.DISABLE_EXPAND);
    311 
    312         mToastString = null;
    313         mSending = false;
    314         mAttached = true;
    315 
    316         if (!mHardwareAccelerated) {
    317             mPreAnimator.start();
    318         } // else, we will start the animation once we get the hardware surface
    319     }
    320 
    321     /** Show starting send animation */
    322     public void showStartSend() {
    323         if (!mAttached) return;
    324 
    325         // Update the starting scale - touchscreen-mashers may trigger
    326         // this before the pre-animation completes.
    327         float currentScale = mScreenshotView.getScaleX();
    328         PropertyValuesHolder postX = PropertyValuesHolder.ofFloat("scaleX",
    329                 new float[] {currentScale, 0.0f});
    330         PropertyValuesHolder postY = PropertyValuesHolder.ofFloat("scaleY",
    331                 new float[] {currentScale, 0.0f});
    332 
    333         mSlowSendAnimator.setValues(postX, postY);
    334         mSlowSendAnimator.start();
    335     }
    336 
    337     public void finishAndToast(int finishMode, String toast) {
    338         if (!mAttached) return;
    339         mToastString = toast;
    340 
    341         finish(finishMode);
    342     }
    343 
    344     /** Return to initial state */
    345     public void finish(int finishMode) {
    346         if (!mAttached) return;
    347 
    348         // Stop rendering the fireflies
    349         if (mFireflyRenderer != null) {
    350             mFireflyRenderer.stop();
    351         }
    352 
    353         mTextHint.setVisibility(View.GONE);
    354 
    355         float currentScale = mScreenshotView.getScaleX();
    356         float currentAlpha = mScreenshotView.getAlpha();
    357 
    358         if (finishMode == FINISH_SCALE_UP) {
    359             PropertyValuesHolder scaleUpX = PropertyValuesHolder.ofFloat("scaleX",
    360                     new float[] {currentScale, 1.0f});
    361             PropertyValuesHolder scaleUpY = PropertyValuesHolder.ofFloat("scaleY",
    362                     new float[] {currentScale, 1.0f});
    363             PropertyValuesHolder scaleUpAlpha = PropertyValuesHolder.ofFloat("alpha",
    364                     new float[] {currentAlpha, 1.0f});
    365             mScaleUpAnimator.setValues(scaleUpX, scaleUpY, scaleUpAlpha);
    366 
    367             mScaleUpAnimator.start();
    368         } else if (finishMode == FINISH_SEND_SUCCESS){
    369             // Modify the fast send parameters to match the current scale
    370             PropertyValuesHolder postX = PropertyValuesHolder.ofFloat("scaleX",
    371                     new float[] {currentScale, 0.0f});
    372             PropertyValuesHolder postY = PropertyValuesHolder.ofFloat("scaleY",
    373                     new float[] {currentScale, 0.0f});
    374             PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha",
    375                     new float[] {1.0f, 0.0f});
    376             mFastSendAnimator.setValues(postX, postY, alpha);
    377 
    378             // Reset the fadeIn parameters to start from alpha 1
    379             PropertyValuesHolder fadeIn = PropertyValuesHolder.ofFloat("alpha",
    380                     new float[] {0.0f, 1.0f});
    381             mFadeInAnimator.setValues(fadeIn);
    382 
    383             mSlowSendAnimator.cancel();
    384             mSuccessAnimatorSet.start();
    385         }
    386     }
    387 
    388     public void dismiss() {
    389         if (!mAttached) return;
    390 
    391         // Immediately set to false, to prevent .cancel() calls
    392         // below from immediately calling into dismiss() again.
    393         mAttached = false;
    394         mSurface = null;
    395         mFrameCounterAnimator.cancel();
    396         mPreAnimator.cancel();
    397         mSlowSendAnimator.cancel();
    398         mFastSendAnimator.cancel();
    399         mSuccessAnimatorSet.cancel();
    400         mScaleUpAnimator.cancel();
    401         mWindowManager.removeView(mScreenshotLayout);
    402         mStatusBarManager.disable(StatusBarManager.DISABLE_NONE);
    403         releaseScreenshot();
    404         if (mToastString != null) {
    405             Toast.makeText(mContext, mToastString, Toast.LENGTH_LONG).show();
    406         }
    407         mToastString = null;
    408     }
    409 
    410     public void releaseScreenshot() {
    411         mScreenshotBitmap = null;
    412     }
    413 
    414     /**
    415      * @return the current display rotation in degrees
    416      */
    417     static float getDegreesForRotation(int value) {
    418         switch (value) {
    419         case Surface.ROTATION_90:
    420             return 90f;
    421         case Surface.ROTATION_180:
    422             return 180f;
    423         case Surface.ROTATION_270:
    424             return 270f;
    425         }
    426         return 0f;
    427     }
    428 
    429     /**
    430      * Returns a screenshot of the current display contents.
    431      */
    432     Bitmap createScreenshot() {
    433         // We need to orient the screenshot correctly (and the Surface api seems to
    434         // take screenshots only in the natural orientation of the device :!)
    435 
    436         mDisplay.getRealMetrics(mDisplayMetrics);
    437         boolean hasNavBar =  mContext.getResources().getBoolean(
    438                 com.android.internal.R.bool.config_showNavigationBar);
    439 
    440         float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels};
    441         float degrees = getDegreesForRotation(mDisplay.getRotation());
    442         final int statusBarHeight = mContext.getResources().getDimensionPixelSize(
    443                                         com.android.internal.R.dimen.status_bar_height);
    444 
    445         // Navbar has different sizes, depending on orientation
    446         final int navBarHeight = hasNavBar ? mContext.getResources().getDimensionPixelSize(
    447                                         com.android.internal.R.dimen.navigation_bar_height) : 0;
    448         final int navBarHeightLandscape = hasNavBar ? mContext.getResources().getDimensionPixelSize(
    449                                         com.android.internal.R.dimen.navigation_bar_height_landscape) : 0;
    450 
    451         final int navBarWidth = hasNavBar ? mContext.getResources().getDimensionPixelSize(
    452                                         com.android.internal.R.dimen.navigation_bar_width) : 0;
    453 
    454         boolean requiresRotation = (degrees > 0);
    455         if (requiresRotation) {
    456             // Get the dimensions of the device in its native orientation
    457             mDisplayMatrix.reset();
    458             mDisplayMatrix.preRotate(-degrees);
    459             mDisplayMatrix.mapPoints(dims);
    460             dims[0] = Math.abs(dims[0]);
    461             dims[1] = Math.abs(dims[1]);
    462         }
    463 
    464         Bitmap bitmap = Surface.screenshot((int) dims[0], (int) dims[1]);
    465         // Bail if we couldn't take the screenshot
    466         if (bitmap == null) {
    467             return null;
    468         }
    469 
    470         if (requiresRotation) {
    471             // Rotate the screenshot to the current orientation
    472             Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels,
    473                     mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888);
    474             Canvas c = new Canvas(ss);
    475             c.translate(ss.getWidth() / 2, ss.getHeight() / 2);
    476             c.rotate(360f - degrees);
    477             c.translate(-dims[0] / 2, -dims[1] / 2);
    478             c.drawBitmap(bitmap, 0, 0, null);
    479 
    480             bitmap = ss;
    481         }
    482 
    483         // TODO this is somewhat device-specific; need generic solution.
    484         // Crop off the status bar and the nav bar
    485         // Portrait: 0, statusBarHeight, width, height - status - nav
    486         // Landscape: 0, statusBarHeight, width - navBar, height - status
    487         int newLeft = 0;
    488         int newTop = statusBarHeight;
    489         int newWidth = bitmap.getWidth();
    490         int newHeight = bitmap.getHeight();
    491         float smallestWidth = (float)Math.min(newWidth, newHeight);
    492         float smallestWidthDp = smallestWidth / (mDisplayMetrics.densityDpi / 160f);
    493         if (bitmap.getWidth() < bitmap.getHeight()) {
    494             // Portrait mode: status bar is at the top, navbar bottom, width unchanged
    495             newHeight = bitmap.getHeight() - statusBarHeight - navBarHeight;
    496         } else {
    497             // Landscape mode: status bar is at the top
    498             // Navbar: bottom on >599dp width devices, otherwise to the side
    499             if (smallestWidthDp > 599) {
    500                 newHeight = bitmap.getHeight() - statusBarHeight - navBarHeightLandscape;
    501             } else {
    502                 newHeight = bitmap.getHeight() - statusBarHeight;
    503                 newWidth = bitmap.getWidth() - navBarWidth;
    504             }
    505         }
    506         bitmap = Bitmap.createBitmap(bitmap, newLeft, newTop, newWidth, newHeight);
    507 
    508         return bitmap;
    509     }
    510 
    511     @Override
    512     public void onAnimationStart(Animator animation) {  }
    513 
    514     @Override
    515     public void onAnimationEnd(Animator animation) {
    516         if (animation == mScaleUpAnimator || animation == mSuccessAnimatorSet ||
    517             animation == mFadeInAnimator) {
    518             // These all indicate the end of the animation
    519             dismiss();
    520         } else if (animation == mFastSendAnimator) {
    521             // After sending is done and we've faded out, reset the scale to 1
    522             // so we can fade it back in.
    523             mScreenshotView.setScaleX(1.0f);
    524             mScreenshotView.setScaleY(1.0f);
    525         } else if (animation == mPreAnimator) {
    526             if (mHardwareAccelerated && mAttached && !mSending) {
    527                 mFireflyRenderer.start(mSurface, mSurfaceWidth, mSurfaceHeight);
    528             }
    529         }
    530     }
    531 
    532     @Override
    533     public void onAnimationCancel(Animator animation) {  }
    534 
    535     @Override
    536     public void onAnimationRepeat(Animator animation) {  }
    537 
    538     @Override
    539     public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
    540         // This gets called on animation vsync
    541         if (++mRenderedFrames < 4) {
    542             // For the first 3 frames, call invalidate(); this calls eglSwapBuffers
    543             // on the surface, which will allocate large buffers the first three calls
    544             // as Android uses triple buffering.
    545             mScreenshotLayout.invalidate();
    546         } else {
    547             // Buffers should be allocated, start the real animation
    548             mFrameCounterAnimator.cancel();
    549             mPreAnimator.start();
    550         }
    551     }
    552 
    553     @Override
    554     public boolean onTouch(View v, MotionEvent event) {
    555         if (!mAttached) {
    556             return false;
    557         }
    558         mSending = true;
    559         // Ignore future touches
    560         mScreenshotView.setOnTouchListener(null);
    561 
    562         // Cancel any ongoing animations
    563         mFrameCounterAnimator.cancel();
    564         mPreAnimator.cancel();
    565 
    566         mCallback.onSendConfirmed();
    567         return true;
    568     }
    569 
    570     @Override
    571     public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
    572         if (mHardwareAccelerated && !mSending) {
    573             mRenderedFrames = 0;
    574 
    575             mFrameCounterAnimator.start();
    576             mSurface = surface;
    577             mSurfaceWidth = width;
    578             mSurfaceHeight = height;
    579         }
    580     }
    581 
    582     @Override
    583     public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
    584         // Since we've disabled orientation changes, we can safely ignore this
    585     }
    586 
    587     @Override
    588     public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
    589         mSurface = null;
    590 
    591         return true;
    592     }
    593 
    594     @Override
    595     public void onSurfaceTextureUpdated(SurfaceTexture surface) { }
    596 
    597 }
    598