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