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