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