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