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