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