| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.nfc; |
| |
| import android.animation.Animator; |
| import android.animation.AnimatorSet; |
| import android.animation.ObjectAnimator; |
| import android.animation.PropertyValuesHolder; |
| import android.animation.TimeAnimator; |
| import android.app.ActivityManager; |
| import android.app.StatusBarManager; |
| import android.content.Context; |
| import android.content.pm.ActivityInfo; |
| import android.content.res.Configuration; |
| import android.graphics.Bitmap; |
| import android.graphics.Canvas; |
| import android.graphics.Matrix; |
| import android.graphics.PixelFormat; |
| import android.graphics.SurfaceTexture; |
| import android.os.AsyncTask; |
| import android.os.Binder; |
| import android.util.DisplayMetrics; |
| import android.util.Log; |
| import android.view.Display; |
| import android.view.LayoutInflater; |
| import android.view.MotionEvent; |
| import android.view.Surface; |
| import android.view.SurfaceControl; |
| import android.view.TextureView; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.WindowManager; |
| import android.view.animation.AccelerateDecelerateInterpolator; |
| import android.view.animation.DecelerateInterpolator; |
| import android.widget.ImageView; |
| import android.widget.TextView; |
| import android.widget.Toast; |
| |
| /** |
| * This class is responsible for handling the UI animation |
| * around Android Beam. The animation consists of the following |
| * animators: |
| * |
| * mPreAnimator: scales the screenshot down to INTERMEDIATE_SCALE |
| * mSlowSendAnimator: scales the screenshot down to 0.2f (used as a "send in progress" animation) |
| * mFastSendAnimator: quickly scales the screenshot down to 0.0f (used for send success) |
| * mFadeInAnimator: fades the current activity back in (used after mFastSendAnimator completes) |
| * mScaleUpAnimator: scales the screenshot back up to full screen (used for failure or receiving) |
| * mHintAnimator: Slowly turns up the alpha of the "Touch to Beam" hint |
| * |
| * Possible sequences are: |
| * |
| * mPreAnimator => mSlowSendAnimator => mFastSendAnimator => mFadeInAnimator (send success) |
| * mPreAnimator => mSlowSendAnimator => mScaleUpAnimator (send failure) |
| * mPreAnimator => mScaleUpAnimator (p2p link broken, or data received) |
| * |
| * Note that mFastSendAnimator and mFadeInAnimator are combined in a set, as they |
| * are an atomic animation that cannot be interrupted. |
| * |
| * All methods of this class must be called on the UI thread |
| */ |
| public class SendUi implements Animator.AnimatorListener, View.OnTouchListener, |
| TimeAnimator.TimeListener, TextureView.SurfaceTextureListener { |
| static final String TAG = "SendUi"; |
| |
| static final float INTERMEDIATE_SCALE = 0.6f; |
| |
| static final float[] PRE_SCREENSHOT_SCALE = {1.0f, INTERMEDIATE_SCALE}; |
| static final int PRE_DURATION_MS = 350; |
| |
| static final float[] SEND_SCREENSHOT_SCALE = {INTERMEDIATE_SCALE, 0.2f}; |
| static final int SLOW_SEND_DURATION_MS = 8000; // Stretch out sending over 8s |
| static final int FAST_SEND_DURATION_MS = 350; |
| |
| static final float[] SCALE_UP_SCREENSHOT_SCALE = {INTERMEDIATE_SCALE, 1.0f}; |
| static final int SCALE_UP_DURATION_MS = 300; |
| |
| static final int FADE_IN_DURATION_MS = 250; |
| static final int FADE_IN_START_DELAY_MS = 350; |
| |
| static final int SLIDE_OUT_DURATION_MS = 300; |
| |
| static final float[] BLACK_LAYER_ALPHA_DOWN_RANGE = {0.9f, 0.0f}; |
| static final float[] BLACK_LAYER_ALPHA_UP_RANGE = {0.0f, 0.9f}; |
| |
| static final float[] TEXT_HINT_ALPHA_RANGE = {0.0f, 1.0f}; |
| static final int TEXT_HINT_ALPHA_DURATION_MS = 500; |
| static final int TEXT_HINT_ALPHA_START_DELAY_MS = 300; |
| |
| static final int FINISH_SCALE_UP = 0; |
| static final int FINISH_SEND_SUCCESS = 1; |
| |
| static final int STATE_IDLE = 0; |
| static final int STATE_W4_SCREENSHOT = 1; |
| static final int STATE_W4_SCREENSHOT_PRESEND_REQUESTED = 2; |
| static final int STATE_W4_SCREENSHOT_THEN_STOP = 3; |
| static final int STATE_W4_PRESEND = 4; |
| static final int STATE_W4_CONFIRM = 5; |
| static final int STATE_SENDING = 6; |
| static final int STATE_COMPLETE = 7; |
| |
| // all members are only used on UI thread |
| final WindowManager mWindowManager; |
| final Context mContext; |
| final Display mDisplay; |
| final DisplayMetrics mDisplayMetrics; |
| final Matrix mDisplayMatrix; |
| final WindowManager.LayoutParams mWindowLayoutParams; |
| final LayoutInflater mLayoutInflater; |
| final StatusBarManager mStatusBarManager; |
| final View mScreenshotLayout; |
| final ImageView mScreenshotView; |
| final ImageView mBlackLayer; |
| final TextureView mTextureView; |
| final TextView mTextHint; |
| final TextView mTextRetry; |
| final Callback mCallback; |
| |
| // The mFrameCounter animation is purely used to count down a certain |
| // number of (vsync'd) frames. This is needed because the first 3 |
| // times the animation internally calls eglSwapBuffers(), large buffers |
| // are allocated by the graphics drivers. This causes the animation |
| // to look janky. So on platforms where we can use hardware acceleration, |
| // the animation order is: |
| // Wait for hw surface => start frame counter => start pre-animation after 3 frames |
| // For platforms where no hw acceleration can be used, the pre-animation |
| // is started immediately. |
| final TimeAnimator mFrameCounterAnimator; |
| |
| final ObjectAnimator mPreAnimator; |
| final ObjectAnimator mSlowSendAnimator; |
| final ObjectAnimator mFastSendAnimator; |
| final ObjectAnimator mFadeInAnimator; |
| final ObjectAnimator mHintAnimator; |
| final ObjectAnimator mScaleUpAnimator; |
| final ObjectAnimator mAlphaDownAnimator; |
| final ObjectAnimator mAlphaUpAnimator; |
| final AnimatorSet mSuccessAnimatorSet; |
| |
| // Besides animating the screenshot, the Beam UI also renders |
| // fireflies on platforms where we can do hardware-acceleration. |
| // Firefly rendering is only started once the initial |
| // "pre-animation" has scaled down the screenshot, to avoid |
| // that animation becoming janky. Likewise, the fireflies are |
| // stopped in their tracks as soon as we finish the animation, |
| // to make the finishing animation smooth. |
| final boolean mHardwareAccelerated; |
| final FireflyRenderer mFireflyRenderer; |
| |
| String mToastString; |
| Bitmap mScreenshotBitmap; |
| |
| int mState; |
| int mRenderedFrames; |
| |
| // Used for holding the surface |
| SurfaceTexture mSurface; |
| int mSurfaceWidth; |
| int mSurfaceHeight; |
| |
| interface Callback { |
| public void onSendConfirmed(); |
| } |
| |
| public SendUi(Context context, Callback callback) { |
| mContext = context; |
| mCallback = callback; |
| |
| mDisplayMetrics = new DisplayMetrics(); |
| mDisplayMatrix = new Matrix(); |
| mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); |
| mStatusBarManager = (StatusBarManager) context.getSystemService(Context.STATUS_BAR_SERVICE); |
| |
| mDisplay = mWindowManager.getDefaultDisplay(); |
| |
| mLayoutInflater = (LayoutInflater) |
| context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); |
| mScreenshotLayout = mLayoutInflater.inflate(R.layout.screenshot, null); |
| |
| mScreenshotView = (ImageView) mScreenshotLayout.findViewById(R.id.screenshot); |
| mScreenshotLayout.setFocusable(true); |
| |
| mTextHint = (TextView) mScreenshotLayout.findViewById(R.id.calltoaction); |
| mTextRetry = (TextView) mScreenshotLayout.findViewById(R.id.retrytext); |
| mBlackLayer = (ImageView) mScreenshotLayout.findViewById(R.id.blacklayer); |
| mTextureView = (TextureView) mScreenshotLayout.findViewById(R.id.fireflies); |
| mTextureView.setSurfaceTextureListener(this); |
| |
| // We're only allowed to use hardware acceleration if |
| // isHighEndGfx() returns true - otherwise, we're too limited |
| // on resources to do it. |
| mHardwareAccelerated = ActivityManager.isHighEndGfx(); |
| int hwAccelerationFlags = mHardwareAccelerated ? |
| WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED : 0; |
| |
| mWindowLayoutParams = new WindowManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, |
| ViewGroup.LayoutParams.MATCH_PARENT, 0, 0, |
| WindowManager.LayoutParams.TYPE_SYSTEM_ALERT, |
| WindowManager.LayoutParams.FLAG_FULLSCREEN |
| | hwAccelerationFlags |
| | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, |
| PixelFormat.OPAQUE); |
| mWindowLayoutParams.privateFlags |= |
| WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; |
| mWindowLayoutParams.token = new Binder(); |
| |
| mFrameCounterAnimator = new TimeAnimator(); |
| mFrameCounterAnimator.setTimeListener(this); |
| |
| PropertyValuesHolder preX = PropertyValuesHolder.ofFloat("scaleX", PRE_SCREENSHOT_SCALE); |
| PropertyValuesHolder preY = PropertyValuesHolder.ofFloat("scaleY", PRE_SCREENSHOT_SCALE); |
| mPreAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, preX, preY); |
| mPreAnimator.setInterpolator(new DecelerateInterpolator()); |
| mPreAnimator.setDuration(PRE_DURATION_MS); |
| mPreAnimator.addListener(this); |
| |
| PropertyValuesHolder postX = PropertyValuesHolder.ofFloat("scaleX", SEND_SCREENSHOT_SCALE); |
| PropertyValuesHolder postY = PropertyValuesHolder.ofFloat("scaleY", SEND_SCREENSHOT_SCALE); |
| PropertyValuesHolder alphaDown = PropertyValuesHolder.ofFloat("alpha", |
| new float[]{1.0f, 0.0f}); |
| |
| mSlowSendAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, postX, postY); |
| mSlowSendAnimator.setInterpolator(new DecelerateInterpolator()); |
| mSlowSendAnimator.setDuration(SLOW_SEND_DURATION_MS); |
| |
| mFastSendAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, postX, |
| postY, alphaDown); |
| mFastSendAnimator.setInterpolator(new DecelerateInterpolator()); |
| mFastSendAnimator.setDuration(FAST_SEND_DURATION_MS); |
| mFastSendAnimator.addListener(this); |
| |
| PropertyValuesHolder scaleUpX = PropertyValuesHolder.ofFloat("scaleX", SCALE_UP_SCREENSHOT_SCALE); |
| PropertyValuesHolder scaleUpY = PropertyValuesHolder.ofFloat("scaleY", SCALE_UP_SCREENSHOT_SCALE); |
| |
| mScaleUpAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, scaleUpX, scaleUpY); |
| mScaleUpAnimator.setInterpolator(new DecelerateInterpolator()); |
| mScaleUpAnimator.setDuration(SCALE_UP_DURATION_MS); |
| mScaleUpAnimator.addListener(this); |
| |
| PropertyValuesHolder fadeIn = PropertyValuesHolder.ofFloat("alpha", 1.0f); |
| mFadeInAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, fadeIn); |
| mFadeInAnimator.setInterpolator(new AccelerateDecelerateInterpolator()); |
| mFadeInAnimator.setDuration(FADE_IN_DURATION_MS); |
| mFadeInAnimator.setStartDelay(FADE_IN_START_DELAY_MS); |
| mFadeInAnimator.addListener(this); |
| |
| PropertyValuesHolder alphaUp = PropertyValuesHolder.ofFloat("alpha", TEXT_HINT_ALPHA_RANGE); |
| mHintAnimator = ObjectAnimator.ofPropertyValuesHolder(mTextHint, alphaUp); |
| mHintAnimator.setInterpolator(null); |
| mHintAnimator.setDuration(TEXT_HINT_ALPHA_DURATION_MS); |
| mHintAnimator.setStartDelay(TEXT_HINT_ALPHA_START_DELAY_MS); |
| |
| alphaDown = PropertyValuesHolder.ofFloat("alpha", BLACK_LAYER_ALPHA_DOWN_RANGE); |
| mAlphaDownAnimator = ObjectAnimator.ofPropertyValuesHolder(mBlackLayer, alphaDown); |
| mAlphaDownAnimator.setInterpolator(new DecelerateInterpolator()); |
| mAlphaDownAnimator.setDuration(400); |
| |
| alphaUp = PropertyValuesHolder.ofFloat("alpha", BLACK_LAYER_ALPHA_UP_RANGE); |
| mAlphaUpAnimator = ObjectAnimator.ofPropertyValuesHolder(mBlackLayer, alphaUp); |
| mAlphaUpAnimator.setInterpolator(new DecelerateInterpolator()); |
| mAlphaUpAnimator.setDuration(200); |
| |
| mSuccessAnimatorSet = new AnimatorSet(); |
| mSuccessAnimatorSet.playSequentially(mFastSendAnimator, mFadeInAnimator); |
| |
| if (mHardwareAccelerated) { |
| mFireflyRenderer = new FireflyRenderer(context); |
| } else { |
| mFireflyRenderer = null; |
| } |
| mState = STATE_IDLE; |
| } |
| |
| public void takeScreenshot() { |
| // There's no point in taking the screenshot if |
| // we're still finishing the previous animation. |
| if (mState >= STATE_W4_CONFIRM) { |
| return; |
| } |
| mState = STATE_W4_SCREENSHOT; |
| new ScreenshotTask().execute(); |
| } |
| |
| /** Show pre-send animation */ |
| public void showPreSend() { |
| switch (mState) { |
| case STATE_IDLE: |
| Log.e(TAG, "Unexpected showPreSend() in STATE_IDLE"); |
| return; |
| case STATE_W4_SCREENSHOT: |
| // Still waiting for screenshot, store request in state |
| // and wait for screenshot completion. |
| mState = STATE_W4_SCREENSHOT_PRESEND_REQUESTED; |
| return; |
| case STATE_W4_SCREENSHOT_PRESEND_REQUESTED: |
| Log.e(TAG, "Unexpected showPreSend() in STATE_W4_SCREENSHOT_PRESEND_REQUESTED"); |
| return; |
| case STATE_W4_PRESEND: |
| // Expected path, continue below |
| break; |
| default: |
| Log.e(TAG, "Unexpected showPreSend() in state " + Integer.toString(mState)); |
| return; |
| } |
| // Update display metrics |
| mDisplay.getRealMetrics(mDisplayMetrics); |
| |
| final int statusBarHeight = mContext.getResources().getDimensionPixelSize( |
| com.android.internal.R.dimen.status_bar_height); |
| |
| mBlackLayer.setVisibility(View.GONE); |
| mBlackLayer.setAlpha(0f); |
| mScreenshotLayout.setOnTouchListener(this); |
| mScreenshotView.setImageBitmap(mScreenshotBitmap); |
| mScreenshotView.setTranslationX(0f); |
| mScreenshotView.setAlpha(1.0f); |
| mScreenshotView.setPadding(0, statusBarHeight, 0, 0); |
| |
| mScreenshotLayout.requestFocus(); |
| |
| mTextHint.setText(mContext.getResources().getString(R.string.touch)); |
| mTextHint.setAlpha(0.0f); |
| mTextHint.setVisibility(View.VISIBLE); |
| mHintAnimator.start(); |
| |
| // Lock the orientation. |
| // The orientation from the configuration does not specify whether |
| // the orientation is reverse or not (ie landscape or reverse landscape). |
| // So we have to use SENSOR_LANDSCAPE or SENSOR_PORTRAIT to make sure |
| // we lock in portrait / landscape and have the sensor determine |
| // which way is up. |
| int orientation = mContext.getResources().getConfiguration().orientation; |
| |
| switch (orientation) { |
| case Configuration.ORIENTATION_LANDSCAPE: |
| mWindowLayoutParams.screenOrientation = |
| ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE; |
| break; |
| case Configuration.ORIENTATION_PORTRAIT: |
| mWindowLayoutParams.screenOrientation = |
| ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT; |
| break; |
| default: |
| mWindowLayoutParams.screenOrientation = |
| ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT; |
| break; |
| } |
| |
| mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); |
| // Disable statusbar pull-down |
| mStatusBarManager.disable(StatusBarManager.DISABLE_EXPAND); |
| |
| mToastString = null; |
| |
| if (!mHardwareAccelerated) { |
| mPreAnimator.start(); |
| } // else, we will start the animation once we get the hardware surface |
| mState = STATE_W4_CONFIRM; |
| } |
| |
| /** Show starting send animation */ |
| public void showStartSend() { |
| if (mState < STATE_SENDING) return; |
| |
| mTextRetry.setVisibility(View.GONE); |
| // Update the starting scale - touchscreen-mashers may trigger |
| // this before the pre-animation completes. |
| float currentScale = mScreenshotView.getScaleX(); |
| PropertyValuesHolder postX = PropertyValuesHolder.ofFloat("scaleX", |
| new float[] {currentScale, 0.0f}); |
| PropertyValuesHolder postY = PropertyValuesHolder.ofFloat("scaleY", |
| new float[] {currentScale, 0.0f}); |
| |
| mSlowSendAnimator.setValues(postX, postY); |
| |
| float currentAlpha = mBlackLayer.getAlpha(); |
| if (mBlackLayer.isShown() && currentAlpha > 0.0f) { |
| PropertyValuesHolder alphaDown = PropertyValuesHolder.ofFloat("alpha", |
| new float[] {currentAlpha, 0.0f}); |
| mAlphaDownAnimator.setValues(alphaDown); |
| mAlphaDownAnimator.start(); |
| } |
| mSlowSendAnimator.start(); |
| } |
| |
| public void finishAndToast(int finishMode, String toast) { |
| mToastString = toast; |
| |
| finish(finishMode); |
| } |
| |
| /** Return to initial state */ |
| public void finish(int finishMode) { |
| switch (mState) { |
| case STATE_IDLE: |
| return; |
| case STATE_W4_SCREENSHOT: |
| case STATE_W4_SCREENSHOT_PRESEND_REQUESTED: |
| // Screenshot is still being captured on a separate thread. |
| // Update state, and stop everything when the capture is done. |
| mState = STATE_W4_SCREENSHOT_THEN_STOP; |
| return; |
| case STATE_W4_SCREENSHOT_THEN_STOP: |
| Log.e(TAG, "Unexpected call to finish() in STATE_W4_SCREENSHOT_THEN_STOP"); |
| return; |
| case STATE_W4_PRESEND: |
| // We didn't build up any animation state yet, but |
| // did store the bitmap. Clear out the bitmap, reset |
| // state and bail. |
| mScreenshotBitmap = null; |
| mState = STATE_IDLE; |
| return; |
| default: |
| // We've started animations and attached a view; tear stuff down below. |
| break; |
| } |
| |
| // Stop rendering the fireflies |
| if (mFireflyRenderer != null) { |
| mFireflyRenderer.stop(); |
| } |
| |
| mTextHint.setVisibility(View.GONE); |
| mTextRetry.setVisibility(View.GONE); |
| |
| float currentScale = mScreenshotView.getScaleX(); |
| float currentAlpha = mScreenshotView.getAlpha(); |
| |
| if (finishMode == FINISH_SCALE_UP) { |
| mBlackLayer.setVisibility(View.GONE); |
| PropertyValuesHolder scaleUpX = PropertyValuesHolder.ofFloat("scaleX", |
| new float[] {currentScale, 1.0f}); |
| PropertyValuesHolder scaleUpY = PropertyValuesHolder.ofFloat("scaleY", |
| new float[] {currentScale, 1.0f}); |
| PropertyValuesHolder scaleUpAlpha = PropertyValuesHolder.ofFloat("alpha", |
| new float[] {currentAlpha, 1.0f}); |
| mScaleUpAnimator.setValues(scaleUpX, scaleUpY, scaleUpAlpha); |
| |
| mScaleUpAnimator.start(); |
| } else if (finishMode == FINISH_SEND_SUCCESS){ |
| // Modify the fast send parameters to match the current scale |
| PropertyValuesHolder postX = PropertyValuesHolder.ofFloat("scaleX", |
| new float[] {currentScale, 0.0f}); |
| PropertyValuesHolder postY = PropertyValuesHolder.ofFloat("scaleY", |
| new float[] {currentScale, 0.0f}); |
| PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", |
| new float[] {currentAlpha, 0.0f}); |
| mFastSendAnimator.setValues(postX, postY, alpha); |
| |
| // Reset the fadeIn parameters to start from alpha 1 |
| PropertyValuesHolder fadeIn = PropertyValuesHolder.ofFloat("alpha", |
| new float[] {0.0f, 1.0f}); |
| mFadeInAnimator.setValues(fadeIn); |
| |
| mSlowSendAnimator.cancel(); |
| mSuccessAnimatorSet.start(); |
| } |
| mState = STATE_COMPLETE; |
| } |
| |
| void dismiss() { |
| if (mState < STATE_W4_CONFIRM) return; |
| // Immediately set to IDLE, to prevent .cancel() calls |
| // below from immediately calling into dismiss() again. |
| // (They can do so on the same thread). |
| mState = STATE_IDLE; |
| mSurface = null; |
| mFrameCounterAnimator.cancel(); |
| mPreAnimator.cancel(); |
| mSlowSendAnimator.cancel(); |
| mFastSendAnimator.cancel(); |
| mSuccessAnimatorSet.cancel(); |
| mScaleUpAnimator.cancel(); |
| mAlphaUpAnimator.cancel(); |
| mAlphaDownAnimator.cancel(); |
| mWindowManager.removeView(mScreenshotLayout); |
| mStatusBarManager.disable(StatusBarManager.DISABLE_NONE); |
| mScreenshotBitmap = null; |
| if (mToastString != null) { |
| Toast.makeText(mContext, mToastString, Toast.LENGTH_LONG).show(); |
| } |
| mToastString = null; |
| } |
| |
| /** |
| * @return the current display rotation in degrees |
| */ |
| static float getDegreesForRotation(int value) { |
| switch (value) { |
| case Surface.ROTATION_90: |
| return 90f; |
| case Surface.ROTATION_180: |
| return 180f; |
| case Surface.ROTATION_270: |
| return 270f; |
| } |
| return 0f; |
| } |
| |
| final class ScreenshotTask extends AsyncTask<Void, Void, Bitmap> { |
| @Override |
| protected Bitmap doInBackground(Void... params) { |
| return createScreenshot(); |
| } |
| |
| @Override |
| protected void onPostExecute(Bitmap result) { |
| if (mState == STATE_W4_SCREENSHOT) { |
| // Screenshot done, wait for request to start preSend anim |
| mState = STATE_W4_PRESEND; |
| } else if (mState == STATE_W4_SCREENSHOT_THEN_STOP) { |
| // We were asked to finish, move to idle state and exit |
| mState = STATE_IDLE; |
| } else if (mState == STATE_W4_SCREENSHOT_PRESEND_REQUESTED) { |
| if (result != null) { |
| mScreenshotBitmap = result; |
| mState = STATE_W4_PRESEND; |
| showPreSend(); |
| } else { |
| // Failed to take screenshot; reset state to idle |
| // and don't do anything |
| Log.e(TAG, "Failed to create screenshot"); |
| mState = STATE_IDLE; |
| } |
| } else { |
| Log.e(TAG, "Invalid state on screenshot completion: " + Integer.toString(mState)); |
| } |
| } |
| }; |
| |
| /** |
| * Returns a screenshot of the current display contents. |
| */ |
| Bitmap createScreenshot() { |
| // We need to orient the screenshot correctly (and the Surface api seems to |
| // take screenshots only in the natural orientation of the device :!) |
| |
| mDisplay.getRealMetrics(mDisplayMetrics); |
| boolean hasNavBar = mContext.getResources().getBoolean( |
| com.android.internal.R.bool.config_showNavigationBar); |
| |
| float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels}; |
| float degrees = getDegreesForRotation(mDisplay.getRotation()); |
| final int statusBarHeight = mContext.getResources().getDimensionPixelSize( |
| com.android.internal.R.dimen.status_bar_height); |
| |
| // Navbar has different sizes, depending on orientation |
| final int navBarHeight = hasNavBar ? mContext.getResources().getDimensionPixelSize( |
| com.android.internal.R.dimen.navigation_bar_height) : 0; |
| final int navBarHeightLandscape = hasNavBar ? mContext.getResources().getDimensionPixelSize( |
| com.android.internal.R.dimen.navigation_bar_height_landscape) : 0; |
| |
| final int navBarWidth = hasNavBar ? mContext.getResources().getDimensionPixelSize( |
| com.android.internal.R.dimen.navigation_bar_width) : 0; |
| |
| boolean requiresRotation = (degrees > 0); |
| if (requiresRotation) { |
| // Get the dimensions of the device in its native orientation |
| mDisplayMatrix.reset(); |
| mDisplayMatrix.preRotate(-degrees); |
| mDisplayMatrix.mapPoints(dims); |
| dims[0] = Math.abs(dims[0]); |
| dims[1] = Math.abs(dims[1]); |
| } |
| |
| Bitmap bitmap = SurfaceControl.screenshot((int) dims[0], (int) dims[1]); |
| // Bail if we couldn't take the screenshot |
| if (bitmap == null) { |
| return null; |
| } |
| |
| if (requiresRotation) { |
| // Rotate the screenshot to the current orientation |
| Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels, |
| mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888); |
| Canvas c = new Canvas(ss); |
| c.translate(ss.getWidth() / 2, ss.getHeight() / 2); |
| c.rotate(360f - degrees); |
| c.translate(-dims[0] / 2, -dims[1] / 2); |
| c.drawBitmap(bitmap, 0, 0, null); |
| |
| bitmap = ss; |
| } |
| |
| // TODO this is somewhat device-specific; need generic solution. |
| // Crop off the status bar and the nav bar |
| // Portrait: 0, statusBarHeight, width, height - status - nav |
| // Landscape: 0, statusBarHeight, width - navBar, height - status |
| int newLeft = 0; |
| int newTop = statusBarHeight; |
| int newWidth = bitmap.getWidth(); |
| int newHeight = bitmap.getHeight(); |
| float smallestWidth = (float)Math.min(newWidth, newHeight); |
| float smallestWidthDp = smallestWidth / (mDisplayMetrics.densityDpi / 160f); |
| if (bitmap.getWidth() < bitmap.getHeight()) { |
| // Portrait mode: status bar is at the top, navbar bottom, width unchanged |
| newHeight = bitmap.getHeight() - statusBarHeight - navBarHeight; |
| } else { |
| // Landscape mode: status bar is at the top |
| // Navbar: bottom on >599dp width devices, otherwise to the side |
| if (smallestWidthDp > 599) { |
| newHeight = bitmap.getHeight() - statusBarHeight - navBarHeightLandscape; |
| } else { |
| newHeight = bitmap.getHeight() - statusBarHeight; |
| newWidth = bitmap.getWidth() - navBarWidth; |
| } |
| } |
| bitmap = Bitmap.createBitmap(bitmap, newLeft, newTop, newWidth, newHeight); |
| |
| return bitmap; |
| } |
| |
| @Override |
| public void onAnimationStart(Animator animation) { } |
| |
| @Override |
| public void onAnimationEnd(Animator animation) { |
| if (animation == mScaleUpAnimator || animation == mSuccessAnimatorSet || |
| animation == mFadeInAnimator) { |
| // These all indicate the end of the animation |
| dismiss(); |
| } else if (animation == mFastSendAnimator) { |
| // After sending is done and we've faded out, reset the scale to 1 |
| // so we can fade it back in. |
| mScreenshotView.setScaleX(1.0f); |
| mScreenshotView.setScaleY(1.0f); |
| } else if (animation == mPreAnimator) { |
| if (mHardwareAccelerated && (mState == STATE_W4_CONFIRM)) { |
| mFireflyRenderer.start(mSurface, mSurfaceWidth, mSurfaceHeight); |
| } |
| } |
| } |
| |
| @Override |
| public void onAnimationCancel(Animator animation) { } |
| |
| @Override |
| public void onAnimationRepeat(Animator animation) { } |
| |
| @Override |
| public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) { |
| // This gets called on animation vsync |
| if (++mRenderedFrames < 4) { |
| // For the first 3 frames, call invalidate(); this calls eglSwapBuffers |
| // on the surface, which will allocate large buffers the first three calls |
| // as Android uses triple buffering. |
| mScreenshotLayout.invalidate(); |
| } else { |
| // Buffers should be allocated, start the real animation |
| mFrameCounterAnimator.cancel(); |
| mPreAnimator.start(); |
| } |
| } |
| |
| @Override |
| public boolean onTouch(View v, MotionEvent event) { |
| if (mState != STATE_W4_CONFIRM) { |
| return false; |
| } |
| mState = STATE_SENDING; |
| // Ignore future touches |
| mScreenshotView.setOnTouchListener(null); |
| |
| // Cancel any ongoing animations |
| mFrameCounterAnimator.cancel(); |
| mPreAnimator.cancel(); |
| |
| mCallback.onSendConfirmed(); |
| return true; |
| } |
| |
| @Override |
| public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { |
| if (mHardwareAccelerated && mState < STATE_COMPLETE) { |
| mRenderedFrames = 0; |
| |
| mFrameCounterAnimator.start(); |
| mSurface = surface; |
| mSurfaceWidth = width; |
| mSurfaceHeight = height; |
| } |
| } |
| |
| @Override |
| public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { |
| // Since we've disabled orientation changes, we can safely ignore this |
| } |
| |
| @Override |
| public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { |
| mSurface = null; |
| |
| return true; |
| } |
| |
| @Override |
| public void onSurfaceTextureUpdated(SurfaceTexture surface) { } |
| |
| public void showSendHint() { |
| if (mAlphaDownAnimator.isRunning()) { |
| mAlphaDownAnimator.cancel(); |
| } |
| if (mSlowSendAnimator.isRunning()) { |
| mSlowSendAnimator.cancel(); |
| } |
| mBlackLayer.setScaleX(mScreenshotView.getScaleX()); |
| mBlackLayer.setScaleY(mScreenshotView.getScaleY()); |
| mBlackLayer.setVisibility(View.VISIBLE); |
| mTextHint.setVisibility(View.GONE); |
| |
| mTextRetry.setText(mContext.getResources().getString(R.string.beam_try_again)); |
| mTextRetry.setVisibility(View.VISIBLE); |
| |
| PropertyValuesHolder alphaUp = PropertyValuesHolder.ofFloat("alpha", |
| new float[] {mBlackLayer.getAlpha(), 0.9f}); |
| mAlphaUpAnimator.setValues(alphaUp); |
| mAlphaUpAnimator.start(); |
| } |
| } |