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