OSDN Git Service

DO NOT MERGE. Grant MMS Uri permissions as the calling UID.
[android-x86/frameworks-base.git] / packages / SystemUI / src / com / android / systemui / statusbar / KeyguardAffordanceView.java
1 /*
2  * Copyright (C) 2014 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.systemui.statusbar;
18
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.ArgbEvaluator;
22 import android.animation.PropertyValuesHolder;
23 import android.animation.ValueAnimator;
24 import android.content.Context;
25 import android.graphics.Canvas;
26 import android.graphics.CanvasProperty;
27 import android.graphics.Color;
28 import android.graphics.Paint;
29 import android.graphics.PorterDuff;
30 import android.graphics.drawable.Drawable;
31 import android.util.AttributeSet;
32 import android.view.DisplayListCanvas;
33 import android.view.RenderNodeAnimator;
34 import android.view.View;
35 import android.view.ViewAnimationUtils;
36 import android.view.animation.Interpolator;
37 import android.widget.ImageView;
38
39 import com.android.systemui.Interpolators;
40 import com.android.systemui.R;
41 import com.android.systemui.statusbar.phone.KeyguardAffordanceHelper;
42
43 /**
44  * An ImageView which does not have overlapping renderings commands and therefore does not need a
45  * layer when alpha is changed.
46  */
47 public class KeyguardAffordanceView extends ImageView {
48
49     private static final long CIRCLE_APPEAR_DURATION = 80;
50     private static final long CIRCLE_DISAPPEAR_MAX_DURATION = 200;
51     private static final long NORMAL_ANIMATION_DURATION = 200;
52     public static final float MAX_ICON_SCALE_AMOUNT = 1.5f;
53     public static final float MIN_ICON_SCALE_AMOUNT = 0.8f;
54
55     private final int mMinBackgroundRadius;
56     private final Paint mCirclePaint;
57     private final int mInverseColor;
58     private final int mNormalColor;
59     private final ArgbEvaluator mColorInterpolator;
60     private final FlingAnimationUtils mFlingAnimationUtils;
61     private float mCircleRadius;
62     private int mCenterX;
63     private int mCenterY;
64     private ValueAnimator mCircleAnimator;
65     private ValueAnimator mAlphaAnimator;
66     private ValueAnimator mScaleAnimator;
67     private float mCircleStartValue;
68     private boolean mCircleWillBeHidden;
69     private int[] mTempPoint = new int[2];
70     private float mImageScale = 1f;
71     private int mCircleColor;
72     private boolean mIsLeft;
73     private View mPreviewView;
74     private float mCircleStartRadius;
75     private float mMaxCircleSize;
76     private Animator mPreviewClipper;
77     private float mRestingAlpha = KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT;
78     private boolean mSupportHardware;
79     private boolean mFinishing;
80     private boolean mLaunchingAffordance;
81
82     private CanvasProperty<Float> mHwCircleRadius;
83     private CanvasProperty<Float> mHwCenterX;
84     private CanvasProperty<Float> mHwCenterY;
85     private CanvasProperty<Paint> mHwCirclePaint;
86
87     private AnimatorListenerAdapter mClipEndListener = new AnimatorListenerAdapter() {
88         @Override
89         public void onAnimationEnd(Animator animation) {
90             mPreviewClipper = null;
91         }
92     };
93     private AnimatorListenerAdapter mCircleEndListener = new AnimatorListenerAdapter() {
94         @Override
95         public void onAnimationEnd(Animator animation) {
96             mCircleAnimator = null;
97         }
98     };
99     private AnimatorListenerAdapter mScaleEndListener = new AnimatorListenerAdapter() {
100         @Override
101         public void onAnimationEnd(Animator animation) {
102             mScaleAnimator = null;
103         }
104     };
105     private AnimatorListenerAdapter mAlphaEndListener = new AnimatorListenerAdapter() {
106         @Override
107         public void onAnimationEnd(Animator animation) {
108             mAlphaAnimator = null;
109         }
110     };
111
112     public KeyguardAffordanceView(Context context) {
113         this(context, null);
114     }
115
116     public KeyguardAffordanceView(Context context, AttributeSet attrs) {
117         this(context, attrs, 0);
118     }
119
120     public KeyguardAffordanceView(Context context, AttributeSet attrs, int defStyleAttr) {
121         this(context, attrs, defStyleAttr, 0);
122     }
123
124     public KeyguardAffordanceView(Context context, AttributeSet attrs, int defStyleAttr,
125             int defStyleRes) {
126         super(context, attrs, defStyleAttr, defStyleRes);
127         mCirclePaint = new Paint();
128         mCirclePaint.setAntiAlias(true);
129         mCircleColor = 0xffffffff;
130         mCirclePaint.setColor(mCircleColor);
131
132         mNormalColor = 0xffffffff;
133         mInverseColor = 0xff000000;
134         mMinBackgroundRadius = mContext.getResources().getDimensionPixelSize(
135                 R.dimen.keyguard_affordance_min_background_radius);
136         mColorInterpolator = new ArgbEvaluator();
137         mFlingAnimationUtils = new FlingAnimationUtils(mContext, 0.3f);
138     }
139
140     @Override
141     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
142         super.onLayout(changed, left, top, right, bottom);
143         mCenterX = getWidth() / 2;
144         mCenterY = getHeight() / 2;
145         mMaxCircleSize = getMaxCircleSize();
146     }
147
148     @Override
149     protected void onDraw(Canvas canvas) {
150         mSupportHardware = false;//canvas.isHardwareAccelerated();
151         drawBackgroundCircle(canvas);
152         canvas.save();
153         canvas.scale(mImageScale, mImageScale, getWidth() / 2, getHeight() / 2);
154         super.onDraw(canvas);
155         canvas.restore();
156     }
157
158     public void setPreviewView(View v) {
159         View oldPreviewView = mPreviewView;
160         mPreviewView = v;
161         if (mPreviewView != null) {
162             mPreviewView.setVisibility(mLaunchingAffordance
163                     ? oldPreviewView.getVisibility() : INVISIBLE);
164         }
165     }
166
167     private void updateIconColor() {
168         Drawable drawable = getDrawable().mutate();
169         float alpha = mCircleRadius / mMinBackgroundRadius;
170         alpha = Math.min(1.0f, alpha);
171         int color = (int) mColorInterpolator.evaluate(alpha, mNormalColor, mInverseColor);
172         drawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
173     }
174
175     private void drawBackgroundCircle(Canvas canvas) {
176         if (mCircleRadius > 0 || mFinishing) {
177             if (mFinishing && mSupportHardware) {
178                 DisplayListCanvas displayListCanvas = (DisplayListCanvas) canvas;
179                 displayListCanvas.drawCircle(mHwCenterX, mHwCenterY, mHwCircleRadius,
180                         mHwCirclePaint);
181             } else {
182                 updateCircleColor();
183                 canvas.drawCircle(mCenterX, mCenterY, mCircleRadius, mCirclePaint);
184             }
185         }
186     }
187
188     private void updateCircleColor() {
189         float fraction = 0.5f + 0.5f * Math.max(0.0f, Math.min(1.0f,
190                 (mCircleRadius - mMinBackgroundRadius) / (0.5f * mMinBackgroundRadius)));
191         if (mPreviewView != null && mPreviewView.getVisibility() == VISIBLE) {
192             float finishingFraction = 1 - Math.max(0, mCircleRadius - mCircleStartRadius)
193                     / (mMaxCircleSize - mCircleStartRadius);
194             fraction *= finishingFraction;
195         }
196         int color = Color.argb((int) (Color.alpha(mCircleColor) * fraction),
197                 Color.red(mCircleColor),
198                 Color.green(mCircleColor), Color.blue(mCircleColor));
199         mCirclePaint.setColor(color);
200     }
201
202     public void finishAnimation(float velocity, final Runnable mAnimationEndRunnable) {
203         cancelAnimator(mCircleAnimator);
204         cancelAnimator(mPreviewClipper);
205         mFinishing = true;
206         mCircleStartRadius = mCircleRadius;
207         final float maxCircleSize = getMaxCircleSize();
208         Animator animatorToRadius;
209         if (mSupportHardware) {
210             initHwProperties();
211             animatorToRadius = getRtAnimatorToRadius(maxCircleSize);
212             startRtAlphaFadeIn();
213         } else {
214             animatorToRadius = getAnimatorToRadius(maxCircleSize);
215         }
216         mFlingAnimationUtils.applyDismissing(animatorToRadius, mCircleRadius, maxCircleSize,
217                 velocity, maxCircleSize);
218         animatorToRadius.addListener(new AnimatorListenerAdapter() {
219             @Override
220             public void onAnimationEnd(Animator animation) {
221                 mAnimationEndRunnable.run();
222                 mFinishing = false;
223                 mCircleRadius = maxCircleSize;
224                 invalidate();
225             }
226         });
227         animatorToRadius.start();
228         setImageAlpha(0, true);
229         if (mPreviewView != null) {
230             mPreviewView.setVisibility(View.VISIBLE);
231             mPreviewClipper = ViewAnimationUtils.createCircularReveal(
232                     mPreviewView, getLeft() + mCenterX, getTop() + mCenterY, mCircleRadius,
233                     maxCircleSize);
234             mFlingAnimationUtils.applyDismissing(mPreviewClipper, mCircleRadius, maxCircleSize,
235                     velocity, maxCircleSize);
236             mPreviewClipper.addListener(mClipEndListener);
237             mPreviewClipper.start();
238             if (mSupportHardware) {
239                 startRtCircleFadeOut(animatorToRadius.getDuration());
240             }
241         }
242     }
243
244     /**
245      * Fades in the Circle on the RenderThread. It's used when finishing the circle when it had
246      * alpha 0 in the beginning.
247      */
248     private void startRtAlphaFadeIn() {
249         if (mCircleRadius == 0 && mPreviewView == null) {
250             Paint modifiedPaint = new Paint(mCirclePaint);
251             modifiedPaint.setColor(mCircleColor);
252             modifiedPaint.setAlpha(0);
253             mHwCirclePaint = CanvasProperty.createPaint(modifiedPaint);
254             RenderNodeAnimator animator = new RenderNodeAnimator(mHwCirclePaint,
255                     RenderNodeAnimator.PAINT_ALPHA, 255);
256             animator.setTarget(this);
257             animator.setInterpolator(Interpolators.ALPHA_IN);
258             animator.setDuration(250);
259             animator.start();
260         }
261     }
262
263     public void instantFinishAnimation() {
264         cancelAnimator(mPreviewClipper);
265         if (mPreviewView != null) {
266             mPreviewView.setClipBounds(null);
267             mPreviewView.setVisibility(View.VISIBLE);
268         }
269         mCircleRadius = getMaxCircleSize();
270         setImageAlpha(0, false);
271         invalidate();
272     }
273
274     private void startRtCircleFadeOut(long duration) {
275         RenderNodeAnimator animator = new RenderNodeAnimator(mHwCirclePaint,
276                 RenderNodeAnimator.PAINT_ALPHA, 0);
277         animator.setDuration(duration);
278         animator.setInterpolator(Interpolators.ALPHA_OUT);
279         animator.setTarget(this);
280         animator.start();
281     }
282
283     private Animator getRtAnimatorToRadius(float circleRadius) {
284         RenderNodeAnimator animator = new RenderNodeAnimator(mHwCircleRadius, circleRadius);
285         animator.setTarget(this);
286         return animator;
287     }
288
289     private void initHwProperties() {
290         mHwCenterX = CanvasProperty.createFloat(mCenterX);
291         mHwCenterY = CanvasProperty.createFloat(mCenterY);
292         mHwCirclePaint = CanvasProperty.createPaint(mCirclePaint);
293         mHwCircleRadius = CanvasProperty.createFloat(mCircleRadius);
294     }
295
296     private float getMaxCircleSize() {
297         getLocationInWindow(mTempPoint);
298         float rootWidth = getRootView().getWidth();
299         float width = mTempPoint[0] + mCenterX;
300         width = Math.max(rootWidth - width, width);
301         float height = mTempPoint[1] + mCenterY;
302         return (float) Math.hypot(width, height);
303     }
304
305     public void setCircleRadius(float circleRadius) {
306         setCircleRadius(circleRadius, false, false);
307     }
308
309     public void setCircleRadius(float circleRadius, boolean slowAnimation) {
310         setCircleRadius(circleRadius, slowAnimation, false);
311     }
312
313     public void setCircleRadiusWithoutAnimation(float circleRadius) {
314         cancelAnimator(mCircleAnimator);
315         setCircleRadius(circleRadius, false ,true);
316     }
317
318     private void setCircleRadius(float circleRadius, boolean slowAnimation, boolean noAnimation) {
319
320         // Check if we need a new animation
321         boolean radiusHidden = (mCircleAnimator != null && mCircleWillBeHidden)
322                 || (mCircleAnimator == null && mCircleRadius == 0.0f);
323         boolean nowHidden = circleRadius == 0.0f;
324         boolean radiusNeedsAnimation = (radiusHidden != nowHidden) && !noAnimation;
325         if (!radiusNeedsAnimation) {
326             if (mCircleAnimator == null) {
327                 mCircleRadius = circleRadius;
328                 updateIconColor();
329                 invalidate();
330                 if (nowHidden) {
331                     if (mPreviewView != null) {
332                         mPreviewView.setVisibility(View.INVISIBLE);
333                     }
334                 }
335             } else if (!mCircleWillBeHidden) {
336
337                 // We just update the end value
338                 float diff = circleRadius - mMinBackgroundRadius;
339                 PropertyValuesHolder[] values = mCircleAnimator.getValues();
340                 values[0].setFloatValues(mCircleStartValue + diff, circleRadius);
341                 mCircleAnimator.setCurrentPlayTime(mCircleAnimator.getCurrentPlayTime());
342             }
343         } else {
344             cancelAnimator(mCircleAnimator);
345             cancelAnimator(mPreviewClipper);
346             ValueAnimator animator = getAnimatorToRadius(circleRadius);
347             Interpolator interpolator = circleRadius == 0.0f
348                     ? Interpolators.FAST_OUT_LINEAR_IN
349                     : Interpolators.LINEAR_OUT_SLOW_IN;
350             animator.setInterpolator(interpolator);
351             long duration = 250;
352             if (!slowAnimation) {
353                 float durationFactor = Math.abs(mCircleRadius - circleRadius)
354                         / (float) mMinBackgroundRadius;
355                 duration = (long) (CIRCLE_APPEAR_DURATION * durationFactor);
356                 duration = Math.min(duration, CIRCLE_DISAPPEAR_MAX_DURATION);
357             }
358             animator.setDuration(duration);
359             animator.start();
360             if (mPreviewView != null && mPreviewView.getVisibility() == View.VISIBLE) {
361                 mPreviewView.setVisibility(View.VISIBLE);
362                 mPreviewClipper = ViewAnimationUtils.createCircularReveal(
363                         mPreviewView, getLeft() + mCenterX, getTop() + mCenterY, mCircleRadius,
364                         circleRadius);
365                 mPreviewClipper.setInterpolator(interpolator);
366                 mPreviewClipper.setDuration(duration);
367                 mPreviewClipper.addListener(mClipEndListener);
368                 mPreviewClipper.addListener(new AnimatorListenerAdapter() {
369                     @Override
370                     public void onAnimationEnd(Animator animation) {
371                         mPreviewView.setVisibility(View.INVISIBLE);
372                     }
373                 });
374                 mPreviewClipper.start();
375             }
376         }
377     }
378
379     private ValueAnimator getAnimatorToRadius(float circleRadius) {
380         ValueAnimator animator = ValueAnimator.ofFloat(mCircleRadius, circleRadius);
381         mCircleAnimator = animator;
382         mCircleStartValue = mCircleRadius;
383         mCircleWillBeHidden = circleRadius == 0.0f;
384         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
385             @Override
386             public void onAnimationUpdate(ValueAnimator animation) {
387                 mCircleRadius = (float) animation.getAnimatedValue();
388                 updateIconColor();
389                 invalidate();
390             }
391         });
392         animator.addListener(mCircleEndListener);
393         return animator;
394     }
395
396     private void cancelAnimator(Animator animator) {
397         if (animator != null) {
398             animator.cancel();
399         }
400     }
401
402     public void setImageScale(float imageScale, boolean animate) {
403         setImageScale(imageScale, animate, -1, null);
404     }
405
406     /**
407      * Sets the scale of the containing image
408      *
409      * @param imageScale The new Scale.
410      * @param animate Should an animation be performed
411      * @param duration If animate, whats the duration? When -1 we take the default duration
412      * @param interpolator If animate, whats the interpolator? When null we take the default
413      *                     interpolator.
414      */
415     public void setImageScale(float imageScale, boolean animate, long duration,
416             Interpolator interpolator) {
417         cancelAnimator(mScaleAnimator);
418         if (!animate) {
419             mImageScale = imageScale;
420             invalidate();
421         } else {
422             ValueAnimator animator = ValueAnimator.ofFloat(mImageScale, imageScale);
423             mScaleAnimator = animator;
424             animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
425                 @Override
426                 public void onAnimationUpdate(ValueAnimator animation) {
427                     mImageScale = (float) animation.getAnimatedValue();
428                     invalidate();
429                 }
430             });
431             animator.addListener(mScaleEndListener);
432             if (interpolator == null) {
433                 interpolator = imageScale == 0.0f
434                         ? Interpolators.FAST_OUT_LINEAR_IN
435                         : Interpolators.LINEAR_OUT_SLOW_IN;
436             }
437             animator.setInterpolator(interpolator);
438             if (duration == -1) {
439                 float durationFactor = Math.abs(mImageScale - imageScale)
440                         / (1.0f - MIN_ICON_SCALE_AMOUNT);
441                 durationFactor = Math.min(1.0f, durationFactor);
442                 duration = (long) (NORMAL_ANIMATION_DURATION * durationFactor);
443             }
444             animator.setDuration(duration);
445             animator.start();
446         }
447     }
448
449     public void setRestingAlpha(float alpha) {
450         mRestingAlpha = alpha;
451
452         // TODO: Handle the case an animation is playing.
453         setImageAlpha(alpha, false);
454     }
455
456     public float getRestingAlpha() {
457         return mRestingAlpha;
458     }
459
460     public void setImageAlpha(float alpha, boolean animate) {
461         setImageAlpha(alpha, animate, -1, null, null);
462     }
463
464     /**
465      * Sets the alpha of the containing image
466      *
467      * @param alpha The new alpha.
468      * @param animate Should an animation be performed
469      * @param duration If animate, whats the duration? When -1 we take the default duration
470      * @param interpolator If animate, whats the interpolator? When null we take the default
471      *                     interpolator.
472      */
473     public void setImageAlpha(float alpha, boolean animate, long duration,
474             Interpolator interpolator, Runnable runnable) {
475         cancelAnimator(mAlphaAnimator);
476         alpha = mLaunchingAffordance ? 0 : alpha;
477         int endAlpha = (int) (alpha * 255);
478         final Drawable background = getBackground();
479         if (!animate) {
480             if (background != null) background.mutate().setAlpha(endAlpha);
481             setImageAlpha(endAlpha);
482         } else {
483             int currentAlpha = getImageAlpha();
484             ValueAnimator animator = ValueAnimator.ofInt(currentAlpha, endAlpha);
485             mAlphaAnimator = animator;
486             animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
487                 @Override
488                 public void onAnimationUpdate(ValueAnimator animation) {
489                     int alpha = (int) animation.getAnimatedValue();
490                     if (background != null) background.mutate().setAlpha(alpha);
491                     setImageAlpha(alpha);
492                 }
493             });
494             animator.addListener(mAlphaEndListener);
495             if (interpolator == null) {
496                 interpolator = alpha == 0.0f
497                         ? Interpolators.FAST_OUT_LINEAR_IN
498                         : Interpolators.LINEAR_OUT_SLOW_IN;
499             }
500             animator.setInterpolator(interpolator);
501             if (duration == -1) {
502                 float durationFactor = Math.abs(currentAlpha - endAlpha) / 255f;
503                 durationFactor = Math.min(1.0f, durationFactor);
504                 duration = (long) (NORMAL_ANIMATION_DURATION * durationFactor);
505             }
506             animator.setDuration(duration);
507             if (runnable != null) {
508                 animator.addListener(getEndListener(runnable));
509             }
510             animator.start();
511         }
512     }
513
514     private Animator.AnimatorListener getEndListener(final Runnable runnable) {
515         return new AnimatorListenerAdapter() {
516             boolean mCancelled;
517             @Override
518             public void onAnimationCancel(Animator animation) {
519                 mCancelled = true;
520             }
521
522             @Override
523             public void onAnimationEnd(Animator animation) {
524                 if (!mCancelled) {
525                     runnable.run();
526                 }
527             }
528         };
529     }
530
531     public float getCircleRadius() {
532         return mCircleRadius;
533     }
534
535     @Override
536     public boolean performClick() {
537         if (isClickable()) {
538             return super.performClick();
539         } else {
540             return false;
541         }
542     }
543
544     public void setLaunchingAffordance(boolean launchingAffordance) {
545         mLaunchingAffordance = launchingAffordance;
546     }
547 }