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