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