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