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