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