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