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