• 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 android.animation.Animator;
20 import android.animation.AnimatorSet;
21 import android.animation.ObjectAnimator;
22 import android.animation.PropertyValuesHolder;
23 import android.animation.TimeAnimator;
24 import android.app.ActivityManager;
25 import android.app.StatusBarManager;
26 import android.content.Context;
27 import android.content.pm.ActivityInfo;
28 import android.content.res.Configuration;
29 import android.graphics.Bitmap;
30 import android.graphics.Canvas;
31 import android.graphics.Matrix;
32 import android.graphics.PixelFormat;
33 import android.graphics.SurfaceTexture;
34 import android.os.AsyncTask;
35 import android.os.Binder;
36 import android.util.DisplayMetrics;
37 import android.util.Log;
38 import android.view.Display;
39 import android.view.LayoutInflater;
40 import android.view.MotionEvent;
41 import android.view.Surface;
42 import android.view.SurfaceControl;
43 import android.view.TextureView;
44 import android.view.View;
45 import android.view.ViewGroup;
46 import android.view.WindowManager;
47 import android.view.animation.AccelerateDecelerateInterpolator;
48 import android.view.animation.DecelerateInterpolator;
49 import android.widget.ImageView;
50 import android.widget.TextView;
51 import 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  */
76 public 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 {
onSendConfirmed()176         public void onSendConfirmed();
177     }
178 
SendUi(Context context, Callback callback)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 
takeScreenshot()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 */
showPreSend()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 */
showStartSend()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 
finishAndToast(int finishMode, String toast)400     public void finishAndToast(int finishMode, String toast) {
401         mToastString = toast;
402 
403         finish(finishMode);
404     }
405 
406     /** Return to initial state */
finish(int finishMode)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 
dismiss()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      */
getDegreesForRotation(int value)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
doInBackground(Void... params)516         protected Bitmap doInBackground(Void... params) {
517             return createScreenshot();
518         }
519 
520         @Override
onPostExecute(Bitmap result)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      */
createScreenshot()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
onAnimationStart(Animator animation)628     public void onAnimationStart(Animator animation) {  }
629 
630     @Override
onAnimationEnd(Animator animation)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
onAnimationCancel(Animator animation)649     public void onAnimationCancel(Animator animation) {  }
650 
651     @Override
onAnimationRepeat(Animator animation)652     public void onAnimationRepeat(Animator animation) {  }
653 
654     @Override
onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime)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
onTouch(View v, MotionEvent event)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
onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height)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
onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height)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
onSurfaceTextureDestroyed(SurfaceTexture surface)704     public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
705         mSurface = null;
706 
707         return true;
708     }
709 
710     @Override
onSurfaceTextureUpdated(SurfaceTexture surface)711     public void onSurfaceTextureUpdated(SurfaceTexture surface) { }
712 
showSendHint()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