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