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.AsyncTask;
     35 import android.os.Binder;
     36 import android.util.DisplayMetrics;
     37 import android.util.Log;
     38 import android.view.Display;
     39 import android.view.LayoutInflater;
     40 import android.view.MotionEvent;
     41 import android.view.Surface;
     42 import android.view.SurfaceControl;
     43 import android.view.TextureView;
     44 import android.view.View;
     45 import android.view.ViewGroup;
     46 import android.view.WindowManager;
     47 import android.view.animation.AccelerateDecelerateInterpolator;
     48 import android.view.animation.DecelerateInterpolator;
     49 import android.widget.ImageView;
     50 import android.widget.TextView;
     51 import android.widget.Toast;
     52 
     53 /**
     54  * This class is responsible for handling the UI animation
     55  * around Android Beam. The animation consists of the following
     56  * animators:
     57  *
     58  * mPreAnimator: scales the screenshot down to INTERMEDIATE_SCALE
     59  * mSlowSendAnimator: scales the screenshot down to 0.2f (used as a "send in progress" animation)
     60  * mFastSendAnimator: quickly scales the screenshot down to 0.0f (used for send success)
     61  * mFadeInAnimator: fades the current activity back in (used after mFastSendAnimator completes)
     62  * mScaleUpAnimator: scales the screenshot back up to full screen (used for failure or receiving)
     63  * mHintAnimator: Slowly turns up the alpha of the "Touch to Beam" hint
     64  *
     65  * Possible sequences are:
     66  *
     67  * mPreAnimator => mSlowSendAnimator => mFastSendAnimator => mFadeInAnimator (send success)
     68  * mPreAnimator => mSlowSendAnimator => mScaleUpAnimator (send failure)
     69  * mPreAnimator => mScaleUpAnimator (p2p link broken, or data received)
     70  *
     71  * Note that mFastSendAnimator and mFadeInAnimator are combined in a set, as they
     72  * are an atomic animation that cannot be interrupted.
     73  *
     74  * All methods of this class must be called on the UI thread
     75  */
     76 public class SendUi implements Animator.AnimatorListener, View.OnTouchListener,
     77         TimeAnimator.TimeListener, TextureView.SurfaceTextureListener {
     78     static final String TAG = "SendUi";
     79 
     80     static final float INTERMEDIATE_SCALE = 0.6f;
     81 
     82     static final float[] PRE_SCREENSHOT_SCALE = {1.0f, INTERMEDIATE_SCALE};
     83     static final int PRE_DURATION_MS = 350;
     84 
     85     static final float[] SEND_SCREENSHOT_SCALE = {INTERMEDIATE_SCALE, 0.2f};
     86     static final int SLOW_SEND_DURATION_MS = 8000; // Stretch out sending over 8s
     87     static final int FAST_SEND_DURATION_MS = 350;
     88 
     89     static final float[] SCALE_UP_SCREENSHOT_SCALE = {INTERMEDIATE_SCALE, 1.0f};
     90     static final int SCALE_UP_DURATION_MS = 300;
     91 
     92     static final int FADE_IN_DURATION_MS = 250;
     93     static final int FADE_IN_START_DELAY_MS = 350;
     94 
     95     static final int SLIDE_OUT_DURATION_MS = 300;
     96 
     97     static final float[] BLACK_LAYER_ALPHA_DOWN_RANGE = {0.9f, 0.0f};
     98     static final float[] BLACK_LAYER_ALPHA_UP_RANGE = {0.0f, 0.9f};
     99 
    100     static final float[] TEXT_HINT_ALPHA_RANGE = {0.0f, 1.0f};
    101     static final int TEXT_HINT_ALPHA_DURATION_MS = 500;
    102     static final int TEXT_HINT_ALPHA_START_DELAY_MS = 300;
    103 
    104     static final int FINISH_SCALE_UP = 0;
    105     static final int FINISH_SEND_SUCCESS = 1;
    106 
    107     static final int STATE_IDLE = 0;
    108     static final int STATE_W4_SCREENSHOT = 1;
    109     static final int STATE_W4_SCREENSHOT_PRESEND_REQUESTED = 2;
    110     static final int STATE_W4_SCREENSHOT_THEN_STOP = 3;
    111     static final int STATE_W4_PRESEND = 4;
    112     static final int STATE_W4_CONFIRM = 5;
    113     static final int STATE_SENDING = 6;
    114     static final int STATE_COMPLETE = 7;
    115 
    116     // all members are only used on UI thread
    117     final WindowManager mWindowManager;
    118     final Context mContext;
    119     final Display mDisplay;
    120     final DisplayMetrics mDisplayMetrics;
    121     final Matrix mDisplayMatrix;
    122     final WindowManager.LayoutParams mWindowLayoutParams;
    123     final LayoutInflater mLayoutInflater;
    124     final StatusBarManager mStatusBarManager;
    125     final View mScreenshotLayout;
    126     final ImageView mScreenshotView;
    127     final ImageView mBlackLayer;
    128     final TextureView mTextureView;
    129     final TextView mTextHint;
    130     final TextView mTextRetry;
    131     final Callback mCallback;
    132 
    133     // The mFrameCounter animation is purely used to count down a certain
    134     // number of (vsync'd) frames. This is needed because the first 3
    135     // times the animation internally calls eglSwapBuffers(), large buffers
    136     // are allocated by the graphics drivers. This causes the animation
    137     // to look janky. So on platforms where we can use hardware acceleration,
    138     // the animation order is:
    139     // Wait for hw surface => start frame counter => start pre-animation after 3 frames
    140     // For platforms where no hw acceleration can be used, the pre-animation
    141     // is started immediately.
    142     final TimeAnimator mFrameCounterAnimator;
    143 
    144     final ObjectAnimator mPreAnimator;
    145     final ObjectAnimator mSlowSendAnimator;
    146     final ObjectAnimator mFastSendAnimator;
    147     final ObjectAnimator mFadeInAnimator;
    148     final ObjectAnimator mHintAnimator;
    149     final ObjectAnimator mScaleUpAnimator;
    150     final ObjectAnimator mAlphaDownAnimator;
    151     final ObjectAnimator mAlphaUpAnimator;
    152     final AnimatorSet mSuccessAnimatorSet;
    153 
    154     // Besides animating the screenshot, the Beam UI also renders
    155     // fireflies on platforms where we can do hardware-acceleration.
    156     // Firefly rendering is only started once the initial
    157     // "pre-animation" has scaled down the screenshot, to avoid
    158     // that animation becoming janky. Likewise, the fireflies are
    159     // stopped in their tracks as soon as we finish the animation,
    160     // to make the finishing animation smooth.
    161     final boolean mHardwareAccelerated;
    162     final FireflyRenderer mFireflyRenderer;
    163 
    164     String mToastString;
    165     Bitmap mScreenshotBitmap;
    166 
    167     int mState;
    168     int mRenderedFrames;
    169 
    170     // Used for holding the surface
    171     SurfaceTexture mSurface;
    172     int mSurfaceWidth;
    173     int mSurfaceHeight;
    174 
    175     interface Callback {
    176         public void onSendConfirmed();
    177     }
    178 
    179     public SendUi(Context context, Callback callback) {
    180         mContext = context;
    181         mCallback = callback;
    182 
    183         mDisplayMetrics = new DisplayMetrics();
    184         mDisplayMatrix = new Matrix();
    185         mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    186         mStatusBarManager = (StatusBarManager) context.getSystemService(Context.STATUS_BAR_SERVICE);
    187 
    188         mDisplay = mWindowManager.getDefaultDisplay();
    189 
    190         mLayoutInflater = (LayoutInflater)
    191                 context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    192         mScreenshotLayout = mLayoutInflater.inflate(R.layout.screenshot, null);
    193 
    194         mScreenshotView = (ImageView) mScreenshotLayout.findViewById(R.id.screenshot);
    195         mScreenshotLayout.setFocusable(true);
    196 
    197         mTextHint = (TextView) mScreenshotLayout.findViewById(R.id.calltoaction);
    198         mTextRetry = (TextView) mScreenshotLayout.findViewById(R.id.retrytext);
    199         mBlackLayer = (ImageView) mScreenshotLayout.findViewById(R.id.blacklayer);
    200         mTextureView = (TextureView) mScreenshotLayout.findViewById(R.id.fireflies);
    201         mTextureView.setSurfaceTextureListener(this);
    202 
    203         // We're only allowed to use hardware acceleration if
    204         // isHighEndGfx() returns true - otherwise, we're too limited
    205         // on resources to do it.
    206         mHardwareAccelerated = ActivityManager.isHighEndGfx();
    207         int hwAccelerationFlags = mHardwareAccelerated ?
    208                 WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED : 0;
    209 
    210         mWindowLayoutParams = new WindowManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
    211                 ViewGroup.LayoutParams.MATCH_PARENT, 0, 0,
    212                 WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
    213                 WindowManager.LayoutParams.FLAG_FULLSCREEN
    214                 | hwAccelerationFlags
    215                 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
    216                 PixelFormat.OPAQUE);
    217         mWindowLayoutParams.privateFlags |=
    218                 WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
    219         mWindowLayoutParams.token = new Binder();
    220 
    221         mFrameCounterAnimator = new TimeAnimator();
    222         mFrameCounterAnimator.setTimeListener(this);
    223 
    224         PropertyValuesHolder preX = PropertyValuesHolder.ofFloat("scaleX", PRE_SCREENSHOT_SCALE);
    225         PropertyValuesHolder preY = PropertyValuesHolder.ofFloat("scaleY", PRE_SCREENSHOT_SCALE);
    226         mPreAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, preX, preY);
    227         mPreAnimator.setInterpolator(new DecelerateInterpolator());
    228         mPreAnimator.setDuration(PRE_DURATION_MS);
    229         mPreAnimator.addListener(this);
    230 
    231         PropertyValuesHolder postX = PropertyValuesHolder.ofFloat("scaleX", SEND_SCREENSHOT_SCALE);
    232         PropertyValuesHolder postY = PropertyValuesHolder.ofFloat("scaleY", SEND_SCREENSHOT_SCALE);
    233         PropertyValuesHolder alphaDown = PropertyValuesHolder.ofFloat("alpha",
    234                 new float[]{1.0f, 0.0f});
    235 
    236         mSlowSendAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, postX, postY);
    237         mSlowSendAnimator.setInterpolator(new DecelerateInterpolator());
    238         mSlowSendAnimator.setDuration(SLOW_SEND_DURATION_MS);
    239 
    240         mFastSendAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, postX,
    241                 postY, alphaDown);
    242         mFastSendAnimator.setInterpolator(new DecelerateInterpolator());
    243         mFastSendAnimator.setDuration(FAST_SEND_DURATION_MS);
    244         mFastSendAnimator.addListener(this);
    245 
    246         PropertyValuesHolder scaleUpX = PropertyValuesHolder.ofFloat("scaleX", SCALE_UP_SCREENSHOT_SCALE);
    247         PropertyValuesHolder scaleUpY = PropertyValuesHolder.ofFloat("scaleY", SCALE_UP_SCREENSHOT_SCALE);
    248 
    249         mScaleUpAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, scaleUpX, scaleUpY);
    250         mScaleUpAnimator.setInterpolator(new DecelerateInterpolator());
    251         mScaleUpAnimator.setDuration(SCALE_UP_DURATION_MS);
    252         mScaleUpAnimator.addListener(this);
    253 
    254         PropertyValuesHolder fadeIn = PropertyValuesHolder.ofFloat("alpha", 1.0f);
    255         mFadeInAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, fadeIn);
    256         mFadeInAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
    257         mFadeInAnimator.setDuration(FADE_IN_DURATION_MS);
    258         mFadeInAnimator.setStartDelay(FADE_IN_START_DELAY_MS);
    259         mFadeInAnimator.addListener(this);
    260 
    261         PropertyValuesHolder alphaUp = PropertyValuesHolder.ofFloat("alpha", TEXT_HINT_ALPHA_RANGE);
    262         mHintAnimator = ObjectAnimator.ofPropertyValuesHolder(mTextHint, alphaUp);
    263         mHintAnimator.setInterpolator(null);
    264         mHintAnimator.setDuration(TEXT_HINT_ALPHA_DURATION_MS);
    265         mHintAnimator.setStartDelay(TEXT_HINT_ALPHA_START_DELAY_MS);
    266 
    267         alphaDown = PropertyValuesHolder.ofFloat("alpha", BLACK_LAYER_ALPHA_DOWN_RANGE);
    268         mAlphaDownAnimator = ObjectAnimator.ofPropertyValuesHolder(mBlackLayer, alphaDown);
    269         mAlphaDownAnimator.setInterpolator(new DecelerateInterpolator());
    270         mAlphaDownAnimator.setDuration(400);
    271 
    272         alphaUp = PropertyValuesHolder.ofFloat("alpha", BLACK_LAYER_ALPHA_UP_RANGE);
    273         mAlphaUpAnimator = ObjectAnimator.ofPropertyValuesHolder(mBlackLayer, alphaUp);
    274         mAlphaUpAnimator.setInterpolator(new DecelerateInterpolator());
    275         mAlphaUpAnimator.setDuration(200);
    276 
    277         mSuccessAnimatorSet = new AnimatorSet();
    278         mSuccessAnimatorSet.playSequentially(mFastSendAnimator, mFadeInAnimator);
    279 
    280         if (mHardwareAccelerated) {
    281             mFireflyRenderer = new FireflyRenderer(context);
    282         } else {
    283             mFireflyRenderer = null;
    284         }
    285         mState = STATE_IDLE;
    286     }
    287 
    288     public void takeScreenshot() {
    289         // There's no point in taking the screenshot if
    290         // we're still finishing the previous animation.
    291         if (mState >= STATE_W4_CONFIRM) {
    292             return;
    293         }
    294         mState = STATE_W4_SCREENSHOT;
    295         new ScreenshotTask().execute();
    296     }
    297 
    298     /** Show pre-send animation */
    299     public void showPreSend() {
    300         switch (mState) {
    301             case STATE_IDLE:
    302                 Log.e(TAG, "Unexpected showPreSend() in STATE_IDLE");
    303                 return;
    304             case STATE_W4_SCREENSHOT:
    305                 // Still waiting for screenshot, store request in state
    306                 // and wait for screenshot completion.
    307                 mState = STATE_W4_SCREENSHOT_PRESEND_REQUESTED;
    308                 return;
    309             case STATE_W4_SCREENSHOT_PRESEND_REQUESTED:
    310                 Log.e(TAG, "Unexpected showPreSend() in STATE_W4_SCREENSHOT_PRESEND_REQUESTED");
    311                 return;
    312             case STATE_W4_PRESEND:
    313                 // Expected path, continue below
    314                 break;
    315             default:
    316                 Log.e(TAG, "Unexpected showPreSend() in state " + Integer.toString(mState));
    317                 return;
    318         }
    319         // Update display metrics
    320         mDisplay.getRealMetrics(mDisplayMetrics);
    321 
    322         final int statusBarHeight = mContext.getResources().getDimensionPixelSize(
    323                                         com.android.internal.R.dimen.status_bar_height);
    324 
    325         mBlackLayer.setVisibility(View.GONE);
    326         mBlackLayer.setAlpha(0f);
    327         mScreenshotLayout.setOnTouchListener(this);
    328         mScreenshotView.setImageBitmap(mScreenshotBitmap);
    329         mScreenshotView.setTranslationX(0f);
    330         mScreenshotView.setAlpha(1.0f);
    331         mScreenshotView.setPadding(0, statusBarHeight, 0, 0);
    332 
    333         mScreenshotLayout.requestFocus();
    334 
    335         mTextHint.setText(mContext.getResources().getString(R.string.touch));
    336         mTextHint.setAlpha(0.0f);
    337         mTextHint.setVisibility(View.VISIBLE);
    338         mHintAnimator.start();
    339 
    340         // Lock the orientation.
    341         // The orientation from the configuration does not specify whether
    342         // the orientation is reverse or not (ie landscape or reverse landscape).
    343         // So we have to use SENSOR_LANDSCAPE or SENSOR_PORTRAIT to make sure
    344         // we lock in portrait / landscape and have the sensor determine
    345         // which way is up.
    346         int orientation = mContext.getResources().getConfiguration().orientation;
    347 
    348         switch (orientation) {
    349             case Configuration.ORIENTATION_LANDSCAPE:
    350                 mWindowLayoutParams.screenOrientation =
    351                         ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
    352                 break;
    353             case Configuration.ORIENTATION_PORTRAIT:
    354                 mWindowLayoutParams.screenOrientation =
    355                         ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;
    356                 break;
    357             default:
    358                 mWindowLayoutParams.screenOrientation =
    359                         ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;
    360                 break;
    361         }
    362 
    363         mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
    364         // Disable statusbar pull-down
    365         mStatusBarManager.disable(StatusBarManager.DISABLE_EXPAND);
    366 
    367         mToastString = null;
    368 
    369         if (!mHardwareAccelerated) {
    370             mPreAnimator.start();
    371         } // else, we will start the animation once we get the hardware surface
    372         mState = STATE_W4_CONFIRM;
    373     }
    374 
    375     /** Show starting send animation */
    376     public void showStartSend() {
    377         if (mState < STATE_SENDING) return;
    378 
    379         mTextRetry.setVisibility(View.GONE);
    380         // Update the starting scale - touchscreen-mashers may trigger
    381         // this before the pre-animation completes.
    382         float currentScale = mScreenshotView.getScaleX();
    383         PropertyValuesHolder postX = PropertyValuesHolder.ofFloat("scaleX",
    384                 new float[] {currentScale, 0.0f});
    385         PropertyValuesHolder postY = PropertyValuesHolder.ofFloat("scaleY",
    386                 new float[] {currentScale, 0.0f});
    387 
    388         mSlowSendAnimator.setValues(postX, postY);
    389 
    390         float currentAlpha = mBlackLayer.getAlpha();
    391         if (mBlackLayer.isShown() && currentAlpha > 0.0f) {
    392             PropertyValuesHolder alphaDown = PropertyValuesHolder.ofFloat("alpha",
    393                     new float[] {currentAlpha, 0.0f});
    394             mAlphaDownAnimator.setValues(alphaDown);
    395             mAlphaDownAnimator.start();
    396         }
    397         mSlowSendAnimator.start();
    398     }
    399 
    400     public void finishAndToast(int finishMode, String toast) {
    401         mToastString = toast;
    402 
    403         finish(finishMode);
    404     }
    405 
    406     /** Return to initial state */
    407     public void finish(int finishMode) {
    408         switch (mState) {
    409             case STATE_IDLE:
    410                 return;
    411             case STATE_W4_SCREENSHOT:
    412             case STATE_W4_SCREENSHOT_PRESEND_REQUESTED:
    413                 // Screenshot is still being captured on a separate thread.
    414                 // Update state, and stop everything when the capture is done.
    415                 mState = STATE_W4_SCREENSHOT_THEN_STOP;
    416                 return;
    417             case STATE_W4_SCREENSHOT_THEN_STOP:
    418                 Log.e(TAG, "Unexpected call to finish() in STATE_W4_SCREENSHOT_THEN_STOP");
    419                 return;
    420             case STATE_W4_PRESEND:
    421                 // We didn't build up any animation state yet, but
    422                 // did store the bitmap. Clear out the bitmap, reset
    423                 // state and bail.
    424                 mScreenshotBitmap = null;
    425                 mState = STATE_IDLE;
    426                 return;
    427             default:
    428                 // We've started animations and attached a view; tear stuff down below.
    429                 break;
    430         }
    431 
    432         // Stop rendering the fireflies
    433         if (mFireflyRenderer != null) {
    434             mFireflyRenderer.stop();
    435         }
    436 
    437         mTextHint.setVisibility(View.GONE);
    438         mTextRetry.setVisibility(View.GONE);
    439 
    440         float currentScale = mScreenshotView.getScaleX();
    441         float currentAlpha = mScreenshotView.getAlpha();
    442 
    443         if (finishMode == FINISH_SCALE_UP) {
    444             mBlackLayer.setVisibility(View.GONE);
    445             PropertyValuesHolder scaleUpX = PropertyValuesHolder.ofFloat("scaleX",
    446                     new float[] {currentScale, 1.0f});
    447             PropertyValuesHolder scaleUpY = PropertyValuesHolder.ofFloat("scaleY",
    448                     new float[] {currentScale, 1.0f});
    449             PropertyValuesHolder scaleUpAlpha = PropertyValuesHolder.ofFloat("alpha",
    450                     new float[] {currentAlpha, 1.0f});
    451             mScaleUpAnimator.setValues(scaleUpX, scaleUpY, scaleUpAlpha);
    452 
    453             mScaleUpAnimator.start();
    454         } else if (finishMode == FINISH_SEND_SUCCESS){
    455             // Modify the fast send parameters to match the current scale
    456             PropertyValuesHolder postX = PropertyValuesHolder.ofFloat("scaleX",
    457                     new float[] {currentScale, 0.0f});
    458             PropertyValuesHolder postY = PropertyValuesHolder.ofFloat("scaleY",
    459                     new float[] {currentScale, 0.0f});
    460             PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha",
    461                     new float[] {currentAlpha, 0.0f});
    462             mFastSendAnimator.setValues(postX, postY, alpha);
    463 
    464             // Reset the fadeIn parameters to start from alpha 1
    465             PropertyValuesHolder fadeIn = PropertyValuesHolder.ofFloat("alpha",
    466                     new float[] {0.0f, 1.0f});
    467             mFadeInAnimator.setValues(fadeIn);
    468 
    469             mSlowSendAnimator.cancel();
    470             mSuccessAnimatorSet.start();
    471         }
    472         mState = STATE_COMPLETE;
    473     }
    474 
    475     void dismiss() {
    476         if (mState < STATE_W4_CONFIRM) return;
    477         // Immediately set to IDLE, to prevent .cancel() calls
    478         // below from immediately calling into dismiss() again.
    479         // (They can do so on the same thread).
    480         mState = STATE_IDLE;
    481         mSurface = null;
    482         mFrameCounterAnimator.cancel();
    483         mPreAnimator.cancel();
    484         mSlowSendAnimator.cancel();
    485         mFastSendAnimator.cancel();
    486         mSuccessAnimatorSet.cancel();
    487         mScaleUpAnimator.cancel();
    488         mAlphaUpAnimator.cancel();
    489         mAlphaDownAnimator.cancel();
    490         mWindowManager.removeView(mScreenshotLayout);
    491         mStatusBarManager.disable(StatusBarManager.DISABLE_NONE);
    492         mScreenshotBitmap = null;
    493         if (mToastString != null) {
    494             Toast.makeText(mContext, mToastString, Toast.LENGTH_LONG).show();
    495         }
    496         mToastString = null;
    497     }
    498 
    499     /**
    500      * @return the current display rotation in degrees
    501      */
    502     static float getDegreesForRotation(int value) {
    503         switch (value) {
    504         case Surface.ROTATION_90:
    505             return 90f;
    506         case Surface.ROTATION_180:
    507             return 180f;
    508         case Surface.ROTATION_270:
    509             return 270f;
    510         }
    511         return 0f;
    512     }
    513 
    514     final class ScreenshotTask extends AsyncTask<Void, Void, Bitmap> {
    515         @Override
    516         protected Bitmap doInBackground(Void... params) {
    517             return createScreenshot();
    518         }
    519 
    520         @Override
    521         protected void onPostExecute(Bitmap result) {
    522             if (mState == STATE_W4_SCREENSHOT) {
    523                 // Screenshot done, wait for request to start preSend anim
    524                 mState = STATE_W4_PRESEND;
    525             } else if (mState == STATE_W4_SCREENSHOT_THEN_STOP) {
    526                 // We were asked to finish, move to idle state and exit
    527                 mState = STATE_IDLE;
    528             } else if (mState == STATE_W4_SCREENSHOT_PRESEND_REQUESTED) {
    529                 if (result != null) {
    530                     mScreenshotBitmap = result;
    531                     mState = STATE_W4_PRESEND;
    532                     showPreSend();
    533                 } else {
    534                     // Failed to take screenshot; reset state to idle
    535                     // and don't do anything
    536                     Log.e(TAG, "Failed to create screenshot");
    537                     mState = STATE_IDLE;
    538                 }
    539             } else {
    540                 Log.e(TAG, "Invalid state on screenshot completion: " + Integer.toString(mState));
    541             }
    542         }
    543     };
    544 
    545     /**
    546      * Returns a screenshot of the current display contents.
    547      */
    548     Bitmap createScreenshot() {
    549         // We need to orient the screenshot correctly (and the Surface api seems to
    550         // take screenshots only in the natural orientation of the device :!)
    551 
    552         mDisplay.getRealMetrics(mDisplayMetrics);
    553         boolean hasNavBar =  mContext.getResources().getBoolean(
    554                 com.android.internal.R.bool.config_showNavigationBar);
    555 
    556         float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels};
    557         float degrees = getDegreesForRotation(mDisplay.getRotation());
    558         final int statusBarHeight = mContext.getResources().getDimensionPixelSize(
    559                                         com.android.internal.R.dimen.status_bar_height);
    560 
    561         // Navbar has different sizes, depending on orientation
    562         final int navBarHeight = hasNavBar ? mContext.getResources().getDimensionPixelSize(
    563                                         com.android.internal.R.dimen.navigation_bar_height) : 0;
    564         final int navBarHeightLandscape = hasNavBar ? mContext.getResources().getDimensionPixelSize(
    565                                         com.android.internal.R.dimen.navigation_bar_height_landscape) : 0;
    566 
    567         final int navBarWidth = hasNavBar ? mContext.getResources().getDimensionPixelSize(
    568                                         com.android.internal.R.dimen.navigation_bar_width) : 0;
    569 
    570         boolean requiresRotation = (degrees > 0);
    571         if (requiresRotation) {
    572             // Get the dimensions of the device in its native orientation
    573             mDisplayMatrix.reset();
    574             mDisplayMatrix.preRotate(-degrees);
    575             mDisplayMatrix.mapPoints(dims);
    576             dims[0] = Math.abs(dims[0]);
    577             dims[1] = Math.abs(dims[1]);
    578         }
    579 
    580         Bitmap bitmap = SurfaceControl.screenshot((int) dims[0], (int) dims[1]);
    581         // Bail if we couldn't take the screenshot
    582         if (bitmap == null) {
    583             return null;
    584         }
    585 
    586         if (requiresRotation) {
    587             // Rotate the screenshot to the current orientation
    588             Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels,
    589                     mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888);
    590             Canvas c = new Canvas(ss);
    591             c.translate(ss.getWidth() / 2, ss.getHeight() / 2);
    592             c.rotate(360f - degrees);
    593             c.translate(-dims[0] / 2, -dims[1] / 2);
    594             c.drawBitmap(bitmap, 0, 0, null);
    595 
    596             bitmap = ss;
    597         }
    598 
    599         // TODO this is somewhat device-specific; need generic solution.
    600         // Crop off the status bar and the nav bar
    601         // Portrait: 0, statusBarHeight, width, height - status - nav
    602         // Landscape: 0, statusBarHeight, width - navBar, height - status
    603         int newLeft = 0;
    604         int newTop = statusBarHeight;
    605         int newWidth = bitmap.getWidth();
    606         int newHeight = bitmap.getHeight();
    607         float smallestWidth = (float)Math.min(newWidth, newHeight);
    608         float smallestWidthDp = smallestWidth / (mDisplayMetrics.densityDpi / 160f);
    609         if (bitmap.getWidth() < bitmap.getHeight()) {
    610             // Portrait mode: status bar is at the top, navbar bottom, width unchanged
    611             newHeight = bitmap.getHeight() - statusBarHeight - navBarHeight;
    612         } else {
    613             // Landscape mode: status bar is at the top
    614             // Navbar: bottom on >599dp width devices, otherwise to the side
    615             if (smallestWidthDp > 599) {
    616                 newHeight = bitmap.getHeight() - statusBarHeight - navBarHeightLandscape;
    617             } else {
    618                 newHeight = bitmap.getHeight() - statusBarHeight;
    619                 newWidth = bitmap.getWidth() - navBarWidth;
    620             }
    621         }
    622         bitmap = Bitmap.createBitmap(bitmap, newLeft, newTop, newWidth, newHeight);
    623 
    624         return bitmap;
    625     }
    626 
    627     @Override
    628     public void onAnimationStart(Animator animation) {  }
    629 
    630     @Override
    631     public void onAnimationEnd(Animator animation) {
    632         if (animation == mScaleUpAnimator || animation == mSuccessAnimatorSet ||
    633             animation == mFadeInAnimator) {
    634             // These all indicate the end of the animation
    635             dismiss();
    636         } else if (animation == mFastSendAnimator) {
    637             // After sending is done and we've faded out, reset the scale to 1
    638             // so we can fade it back in.
    639             mScreenshotView.setScaleX(1.0f);
    640             mScreenshotView.setScaleY(1.0f);
    641         } else if (animation == mPreAnimator) {
    642             if (mHardwareAccelerated && (mState == STATE_W4_CONFIRM)) {
    643                 mFireflyRenderer.start(mSurface, mSurfaceWidth, mSurfaceHeight);
    644             }
    645         }
    646     }
    647 
    648     @Override
    649     public void onAnimationCancel(Animator animation) {  }
    650 
    651     @Override
    652     public void onAnimationRepeat(Animator animation) {  }
    653 
    654     @Override
    655     public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
    656         // This gets called on animation vsync
    657         if (++mRenderedFrames < 4) {
    658             // For the first 3 frames, call invalidate(); this calls eglSwapBuffers
    659             // on the surface, which will allocate large buffers the first three calls
    660             // as Android uses triple buffering.
    661             mScreenshotLayout.invalidate();
    662         } else {
    663             // Buffers should be allocated, start the real animation
    664             mFrameCounterAnimator.cancel();
    665             mPreAnimator.start();
    666         }
    667     }
    668 
    669     @Override
    670     public boolean onTouch(View v, MotionEvent event) {
    671         if (mState != STATE_W4_CONFIRM) {
    672             return false;
    673         }
    674         mState = STATE_SENDING;
    675         // Ignore future touches
    676         mScreenshotView.setOnTouchListener(null);
    677 
    678         // Cancel any ongoing animations
    679         mFrameCounterAnimator.cancel();
    680         mPreAnimator.cancel();
    681 
    682         mCallback.onSendConfirmed();
    683         return true;
    684     }
    685 
    686     @Override
    687     public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
    688         if (mHardwareAccelerated && mState < STATE_COMPLETE) {
    689             mRenderedFrames = 0;
    690 
    691             mFrameCounterAnimator.start();
    692             mSurface = surface;
    693             mSurfaceWidth = width;
    694             mSurfaceHeight = height;
    695         }
    696     }
    697 
    698     @Override
    699     public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
    700         // Since we've disabled orientation changes, we can safely ignore this
    701     }
    702 
    703     @Override
    704     public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
    705         mSurface = null;
    706 
    707         return true;
    708     }
    709 
    710     @Override
    711     public void onSurfaceTextureUpdated(SurfaceTexture surface) { }
    712 
    713     public void showSendHint() {
    714         if (mAlphaDownAnimator.isRunning()) {
    715            mAlphaDownAnimator.cancel();
    716         }
    717         if (mSlowSendAnimator.isRunning()) {
    718             mSlowSendAnimator.cancel();
    719         }
    720         mBlackLayer.setScaleX(mScreenshotView.getScaleX());
    721         mBlackLayer.setScaleY(mScreenshotView.getScaleY());
    722         mBlackLayer.setVisibility(View.VISIBLE);
    723         mTextHint.setVisibility(View.GONE);
    724 
    725         mTextRetry.setText(mContext.getResources().getString(R.string.beam_try_again));
    726         mTextRetry.setVisibility(View.VISIBLE);
    727 
    728         PropertyValuesHolder alphaUp = PropertyValuesHolder.ofFloat("alpha",
    729                 new float[] {mBlackLayer.getAlpha(), 0.9f});
    730         mAlphaUpAnimator.setValues(alphaUp);
    731         mAlphaUpAnimator.start();
    732     }
    733 }
    734