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