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