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