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