SendUi.java revision 69ca6627125c91c44b9e0d8bfa5df83281c2ebe1
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 17package com.android.nfc; 18 19import com.android.nfc3.R; 20 21import android.animation.Animator; 22import android.animation.AnimatorSet; 23import android.animation.ObjectAnimator; 24import android.animation.PropertyValuesHolder; 25import android.content.Context; 26import android.content.pm.ActivityInfo; 27import android.content.res.Configuration; 28import android.graphics.Bitmap; 29import android.graphics.Canvas; 30import android.graphics.Matrix; 31import android.graphics.PixelFormat; 32import android.os.Binder; 33import android.util.DisplayMetrics; 34import android.view.Display; 35import android.view.LayoutInflater; 36import android.view.MotionEvent; 37import android.view.Surface; 38import android.view.View; 39import android.view.ViewGroup; 40import android.view.WindowManager; 41import android.view.animation.AccelerateInterpolator; 42import android.view.animation.DecelerateInterpolator; 43import android.widget.ImageView; 44 45/** 46 * All methods must be called on UI thread 47 */ 48public class SendUi implements Animator.AnimatorListener, View.OnTouchListener { 49 50 static final float INTERMEDIATE_SCALE = 0.6f; 51 52 static final float[] PRE_SCREENSHOT_SCALE = {1.0f, INTERMEDIATE_SCALE}; 53 static final int PRE_DURATION_MS = 300; 54 55 static final float[] CLONE_SCREENSHOT_SCALE = {INTERMEDIATE_SCALE, 0.0f}; 56 static final int SLOW_CLONE_DURATION_MS = 3000; // Stretch out sending over 3s 57 static final int FAST_CLONE_DURATION_MS = 200; 58 59 static final float[] SCALE_UP_SCREENSHOT_SCALE = {INTERMEDIATE_SCALE, 1.0f}; 60 static final int SCALE_UP_DURATION_MS = 300; 61 62 // all members are only used on UI thread 63 final WindowManager mWindowManager; 64 final Context mContext; 65 final Display mDisplay; 66 final DisplayMetrics mDisplayMetrics; 67 final Matrix mDisplayMatrix; 68 final WindowManager.LayoutParams mWindowLayoutParams; 69 final LayoutInflater mLayoutInflater; 70 final View mScreenshotLayout; 71 final ImageView mScreenshotView; 72 final ImageView mCloneView; 73 final Callback mCallback; 74 final ObjectAnimator mPreAnimator; 75 final ObjectAnimator mSlowCloneAnimator; 76 final ObjectAnimator mFastCloneAnimator; 77 final ObjectAnimator mScaleUpAnimator; 78 final AnimatorSet mSuccessAnimatorSet; 79 80 Bitmap mScreenshotBitmap; 81 boolean mAttached; 82 83 interface Callback { 84 public void onSendConfirmed(); 85 } 86 87 public SendUi(Context context, Callback callback) { 88 mContext = context; 89 mCallback = callback; 90 91 mDisplayMetrics = new DisplayMetrics(); 92 mDisplayMatrix = new Matrix(); 93 mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 94 mDisplay = mWindowManager.getDefaultDisplay(); 95 96 mLayoutInflater = (LayoutInflater) 97 context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 98 mScreenshotLayout = mLayoutInflater.inflate(R.layout.screenshot, null); 99 mScreenshotView = (ImageView) mScreenshotLayout.findViewById(R.id.screenshot); 100 mScreenshotLayout.setFocusable(true); 101 102 mCloneView = (ImageView) mScreenshotLayout.findViewById(R.id.clone); 103 104 mWindowLayoutParams = new WindowManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 105 ViewGroup.LayoutParams.MATCH_PARENT, 0, 0, 0, 106 WindowManager.LayoutParams.FLAG_FULLSCREEN 107 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED 108 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED_SYSTEM 109 | WindowManager.LayoutParams.FLAG_KEEP_SURFACE_WHILE_ANIMATING 110 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, 111// | WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY, 112 PixelFormat.OPAQUE); 113 mWindowLayoutParams.token = new Binder(); 114 115 PropertyValuesHolder preX = PropertyValuesHolder.ofFloat("scaleX", PRE_SCREENSHOT_SCALE); 116 PropertyValuesHolder preY = PropertyValuesHolder.ofFloat("scaleY", PRE_SCREENSHOT_SCALE); 117 mPreAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, preX, preY); 118 mPreAnimator.setInterpolator(new DecelerateInterpolator()); 119 mPreAnimator.setDuration(PRE_DURATION_MS); 120 mPreAnimator.addListener(this); 121 122 PropertyValuesHolder postX = PropertyValuesHolder.ofFloat("scaleX", CLONE_SCREENSHOT_SCALE); 123 PropertyValuesHolder postY = PropertyValuesHolder.ofFloat("scaleY", CLONE_SCREENSHOT_SCALE); 124 mSlowCloneAnimator = ObjectAnimator.ofPropertyValuesHolder(mCloneView, postX, postY); 125 mSlowCloneAnimator.setInterpolator(null); // linear 126 mSlowCloneAnimator.setDuration(SLOW_CLONE_DURATION_MS); 127 128 mFastCloneAnimator = ObjectAnimator.ofPropertyValuesHolder(mCloneView, postX, postY); 129 mFastCloneAnimator.setInterpolator(null); // linear 130 mFastCloneAnimator.setDuration(FAST_CLONE_DURATION_MS); 131 132 PropertyValuesHolder scaleUpX = PropertyValuesHolder.ofFloat("scaleX", SCALE_UP_SCREENSHOT_SCALE); 133 PropertyValuesHolder scaleUpY = PropertyValuesHolder.ofFloat("scaleY", SCALE_UP_SCREENSHOT_SCALE); 134 mScaleUpAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, scaleUpX, scaleUpY); 135 mScaleUpAnimator.setInterpolator(new AccelerateInterpolator()); 136 mScaleUpAnimator.setDuration(SCALE_UP_DURATION_MS); 137 mScaleUpAnimator.addListener(this); 138 139 mSuccessAnimatorSet = new AnimatorSet(); 140 mSuccessAnimatorSet.playSequentially(mFastCloneAnimator, mScaleUpAnimator); 141 142 mAttached = false; 143 } 144 145 public void takeScreenshot() { 146 mScreenshotBitmap = createScreenshot(); 147 } 148 149 /** Show pre-send animation */ 150 public void showPreSend() { 151 if (mScreenshotBitmap == null || mAttached) { 152 return; 153 } 154 mScreenshotView.setOnTouchListener(this); 155 156 mScreenshotView.setImageBitmap(mScreenshotBitmap); 157 mScreenshotLayout.requestFocus(); 158 159 mCloneView.setImageBitmap(mScreenshotBitmap); 160 mCloneView.setVisibility(View.GONE); 161 mCloneView.setScaleX(INTERMEDIATE_SCALE); 162 mCloneView.setScaleY(INTERMEDIATE_SCALE); 163 164 mScreenshotView.setAlpha(1.0f); 165 166 // Lock the orientation. 167 // The orientation from the configuration does not specify whether 168 // the orientation is reverse or not (ie landscape or reverse landscape). 169 // So we have to use SENSOR_LANDSCAPE or SENSOR_PORTRAIT to make sure 170 // we lock in portrait / landscape and have the sensor determine 171 // which way is up. 172 int orientation = mContext.getResources().getConfiguration().orientation; 173 174 switch (orientation) { 175 case Configuration.ORIENTATION_LANDSCAPE: 176 mWindowLayoutParams.screenOrientation = 177 ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE; 178 break; 179 case Configuration.ORIENTATION_PORTRAIT: 180 mWindowLayoutParams.screenOrientation = 181 ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT; 182 break; 183 default: 184 mWindowLayoutParams.screenOrientation = 185 ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT; 186 break; 187 } 188 189 mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); 190 mAttached = true; 191 mPreAnimator.start(); 192 } 193 194 /** Show starting send animation */ 195 public void showStartSend() { 196 if (!mAttached) { 197 return; 198 } 199 200 mScreenshotView.setAlpha(0.6f); 201 mCloneView.setScaleX(INTERMEDIATE_SCALE); 202 mCloneView.setScaleY(INTERMEDIATE_SCALE); 203 mCloneView.setVisibility(View.VISIBLE); 204 mSlowCloneAnimator.start(); 205 } 206 207 /** Show post-send animation */ 208 public void showPostSend() { 209 if (!mAttached) { 210 return; 211 } 212 213 mSlowCloneAnimator.cancel(); 214 215 // Modify the fast clone parameters to match the current scale 216 float currentScale = mCloneView.getScaleX(); 217 currentScale = mCloneView.getScaleX(); 218 219 PropertyValuesHolder postX = PropertyValuesHolder.ofFloat("scaleX", new float[] {currentScale, 0.0f}); 220 PropertyValuesHolder postY = PropertyValuesHolder.ofFloat("scaleY", new float[] {currentScale, 0.0f}); 221 mFastCloneAnimator.setValues(postX, postY); 222 223 mSuccessAnimatorSet.start(); 224 } 225 226 /** Return to initial state */ 227 public void finish() { 228 if (!mAttached) { 229 return; 230 } 231 232 mScaleUpAnimator.start(); 233 } 234 235 236 public void dismiss() { 237 if (!mAttached) { 238 return; 239 } 240 mPreAnimator.cancel(); 241 mSlowCloneAnimator.cancel(); 242 mFastCloneAnimator.cancel(); 243 mSuccessAnimatorSet.cancel(); 244 mScaleUpAnimator.cancel(); 245 mWindowManager.removeView(mScreenshotLayout); 246 mAttached = false; 247 releaseScreenshot(); 248 } 249 250 public void releaseScreenshot() { 251 mScreenshotBitmap = null; 252 } 253 254 /** 255 * @return the current display rotation in degrees 256 */ 257 static float getDegreesForRotation(int value) { 258 switch (value) { 259 case Surface.ROTATION_90: 260 return 90f; 261 case Surface.ROTATION_180: 262 return 180f; 263 case Surface.ROTATION_270: 264 return 270f; 265 } 266 return 0f; 267 } 268 269 /** 270 * Returns a screenshot of the current display contents. 271 * @param context Context. 272 * @return 273 */ 274 Bitmap createScreenshot() { 275 // We need to orient the screenshot correctly (and the Surface api seems to 276 // take screenshots only in the natural orientation of the device :!) 277 mDisplay.getRealMetrics(mDisplayMetrics); 278 float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels}; 279 float degrees = getDegreesForRotation(mDisplay.getRotation()); 280 boolean requiresRotation = (degrees > 0); 281 if (requiresRotation) { 282 // Get the dimensions of the device in its native orientation 283 mDisplayMatrix.reset(); 284 mDisplayMatrix.preRotate(-degrees); 285 mDisplayMatrix.mapPoints(dims); 286 dims[0] = Math.abs(dims[0]); 287 dims[1] = Math.abs(dims[1]); 288 } 289 290 Bitmap bitmap = Surface.screenshot((int) dims[0], (int) dims[1]); 291 // Bail if we couldn't take the screenshot 292 if (bitmap == null) { 293 return null; 294 } 295 296 if (requiresRotation) { 297 // Rotate the screenshot to the current orientation 298 Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels, 299 mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888); 300 Canvas c = new Canvas(ss); 301 c.translate(ss.getWidth() / 2, ss.getHeight() / 2); 302 c.rotate(360f - degrees); 303 c.translate(-dims[0] / 2, -dims[1] / 2); 304 c.drawBitmap(bitmap, 0, 0, null); 305 306 bitmap = ss; 307 } 308 309 return bitmap; 310 } 311 312 @Override 313 public void onAnimationStart(Animator animation) { } 314 315 @Override 316 public void onAnimationEnd(Animator animation) { 317 if (animation == mScaleUpAnimator || animation == mSuccessAnimatorSet) { 318 dismiss(); 319 } 320 } 321 322 @Override 323 public void onAnimationCancel(Animator animation) { } 324 325 @Override 326 public void onAnimationRepeat(Animator animation) { } 327 328 @Override 329 public boolean onTouch(View v, MotionEvent event) { 330 if (!mAttached) { 331 return false; 332 } 333 // Ignore future touches 334 mScreenshotView.setOnTouchListener(null); 335 336 mPreAnimator.end(); 337 mCallback.onSendConfirmed(); 338 return true; 339 } 340} 341