• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.nfc;
18 
19 import com.android.internal.policy.PolicyManager;
20 
21 import android.animation.Animator;
22 import android.animation.AnimatorSet;
23 import android.animation.ObjectAnimator;
24 import android.animation.PropertyValuesHolder;
25 import android.animation.TimeAnimator;
26 import android.app.ActivityManager;
27 import android.app.StatusBarManager;
28 import android.content.Context;
29 import android.content.pm.ActivityInfo;
30 import android.content.res.Configuration;
31 import android.graphics.Bitmap;
32 import android.graphics.Canvas;
33 import android.graphics.Matrix;
34 import android.graphics.PixelFormat;
35 import android.graphics.SurfaceTexture;
36 import android.os.AsyncTask;
37 import android.os.Binder;
38 import android.util.DisplayMetrics;
39 import android.util.Log;
40 import android.view.ActionMode;
41 import android.view.Display;
42 import android.view.KeyEvent;
43 import android.view.LayoutInflater;
44 import android.view.Menu;
45 import android.view.MenuItem;
46 import android.view.MotionEvent;
47 import android.view.Surface;
48 import android.view.SurfaceControl;
49 import android.view.TextureView;
50 import android.view.View;
51 import android.view.ViewGroup;
52 import android.view.Window;
53 import android.view.WindowManager;
54 import android.view.WindowManager.LayoutParams;
55 import android.view.accessibility.AccessibilityEvent;
56 import android.view.animation.AccelerateDecelerateInterpolator;
57 import android.view.animation.DecelerateInterpolator;
58 import android.widget.ImageView;
59 import android.widget.TextView;
60 import 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  */
85 public 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 {
onSendConfirmed()189         public void onSendConfirmed();
onCanceled()190         public void onCanceled();
191     }
192 
SendUi(Context context, Callback callback)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 
takeScreenshot()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 */
showPreSend(boolean promptToNfcTap)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 */
showStartSend()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 
finishAndToast(int finishMode, String toast)432     public void finishAndToast(int finishMode, String toast) {
433         mToastString = toast;
434 
435         finish(finishMode);
436     }
437 
438     /** Return to initial state */
finish(int finishMode)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 
dismiss()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      */
getDegreesForRotation(int value)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
doInBackground(Void... params)549         protected Bitmap doInBackground(Void... params) {
550             return createScreenshot();
551         }
552 
553         @Override
onPostExecute(Bitmap result)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      */
createScreenshot()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
onAnimationStart(Animator animation)662     public void onAnimationStart(Animator animation) {  }
663 
664     @Override
onAnimationEnd(Animator animation)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
onAnimationCancel(Animator animation)683     public void onAnimationCancel(Animator animation) {  }
684 
685     @Override
onAnimationRepeat(Animator animation)686     public void onAnimationRepeat(Animator animation) {  }
687 
688     @Override
onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime)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
onTouch(View v, MotionEvent event)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
onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height)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
onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height)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
onSurfaceTextureDestroyed(SurfaceTexture surface)738     public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
739         mSurface = null;
740 
741         return true;
742     }
743 
744     @Override
onSurfaceTextureUpdated(SurfaceTexture surface)745     public void onSurfaceTextureUpdated(SurfaceTexture surface) { }
746 
showSendHint()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
dispatchKeyEvent(KeyEvent event)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
dispatchKeyShortcutEvent(KeyEvent event)784     public boolean dispatchKeyShortcutEvent(KeyEvent event) {
785         return false;
786     }
787 
788     @Override
dispatchTouchEvent(MotionEvent event)789     public boolean dispatchTouchEvent(MotionEvent event) {
790         return mScreenshotLayout.dispatchTouchEvent(event);
791     }
792 
793     @Override
dispatchTrackballEvent(MotionEvent event)794     public boolean dispatchTrackballEvent(MotionEvent event) {
795         return false;
796     }
797 
798     @Override
dispatchGenericMotionEvent(MotionEvent event)799     public boolean dispatchGenericMotionEvent(MotionEvent event) {
800         return false;
801     }
802 
803     @Override
dispatchPopulateAccessibilityEvent(AccessibilityEvent event)804     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
805         return false;
806     }
807 
808     @Override
onCreatePanelView(int featureId)809     public View onCreatePanelView(int featureId) {
810         return null;
811     }
812 
813     @Override
onCreatePanelMenu(int featureId, Menu menu)814     public boolean onCreatePanelMenu(int featureId, Menu menu) {
815         return false;
816     }
817 
818     @Override
onPreparePanel(int featureId, View view, Menu menu)819     public boolean onPreparePanel(int featureId, View view, Menu menu) {
820         return false;
821     }
822 
823     @Override
onMenuOpened(int featureId, Menu menu)824     public boolean onMenuOpened(int featureId, Menu menu) {
825         return false;
826     }
827 
828     @Override
onMenuItemSelected(int featureId, MenuItem item)829     public boolean onMenuItemSelected(int featureId, MenuItem item) {
830         return false;
831     }
832 
833     @Override
onWindowAttributesChanged(LayoutParams attrs)834     public void onWindowAttributesChanged(LayoutParams attrs) {
835     }
836 
837     @Override
onContentChanged()838     public void onContentChanged() {
839     }
840 
841     @Override
onWindowFocusChanged(boolean hasFocus)842     public void onWindowFocusChanged(boolean hasFocus) {
843     }
844 
845     @Override
onAttachedToWindow()846     public void onAttachedToWindow() {
847 
848     }
849 
850     @Override
onDetachedFromWindow()851     public void onDetachedFromWindow() {
852     }
853 
854     @Override
onPanelClosed(int featureId, Menu menu)855     public void onPanelClosed(int featureId, Menu menu) {
856 
857     }
858 
859     @Override
onSearchRequested()860     public boolean onSearchRequested() {
861         return false;
862     }
863 
864     @Override
onWindowStartingActionMode( android.view.ActionMode.Callback callback)865     public ActionMode onWindowStartingActionMode(
866             android.view.ActionMode.Callback callback) {
867         return null;
868     }
869 
870     @Override
onActionModeStarted(ActionMode mode)871     public void onActionModeStarted(ActionMode mode) {
872     }
873 
874     @Override
onActionModeFinished(ActionMode mode)875     public void onActionModeFinished(ActionMode mode) {
876     }
877 }
878