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