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