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