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