Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit baa23274 authored by Selim Cinek's avatar Selim Cinek
Browse files

Implemented new camera affordance

Also fixed a bug in the touch logic where you could close the shade / hint
after going to the camera / phone.

Bug: 15126905
Change-Id: Iadfde56cb68f4048868eedec6bd3456f55823cd9
parent 80440cc8
Loading
Loading
Loading
Loading
+28 −0
Original line number Diff line number Diff line
<!--
  ~ Copyright (C) 2014 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
  -->
<vector xmlns:android="http://schemas.android.com/apk/res/android" >
    <size
        android:width="24dp"
        android:height="24dp"/>

    <viewport
        android:viewportWidth="36.0"
        android:viewportHeight="36.0"/>

    <path
        android:fill="#ffffffff"
        android:pathData="M23.1,11.1l-2.1000004,-2.1000004 -9.0,9.0 9.0,9.0 2.1000004,-2.1000004 -6.8999996,-6.8999996z"/>
</vector>
+3 −3
Original line number Diff line number Diff line
@@ -22,7 +22,7 @@
    android:layout_height="match_parent"
    android:layout_width="match_parent"
    >
    <com.android.systemui.statusbar.AlphaImageView
    <com.android.systemui.statusbar.KeyguardAffordanceView
        android:id="@+id/camera_button"
        android:layout_height="64dp"
        android:layout_width="64dp"
@@ -32,7 +32,7 @@
        android:scaleType="center"
        android:contentDescription="@string/accessibility_camera_button" />

    <com.android.systemui.statusbar.AlphaImageView
    <com.android.systemui.statusbar.KeyguardAffordanceView
        android:id="@+id/phone_button"
        android:layout_height="64dp"
        android:layout_width="64dp"
@@ -52,7 +52,7 @@
        android:textColor="#ffffff"
        android:textAppearance="?android:attr/textAppearanceSmall"/>

    <com.android.systemui.statusbar.AlphaImageView
    <com.android.systemui.statusbar.KeyguardAffordanceView
        android:id="@+id/lock_icon"
        android:layout_width="64dp"
        android:layout_height="64dp"
+9 −3
Original line number Diff line number Diff line
@@ -295,6 +295,15 @@
    <!-- The minimum amount the user needs to swipe to go to the camera / phone. -->
    <dimen name="keyguard_min_swipe_amount">75dp</dimen>

    <!-- The minimum background radius when swiping to a side for the camera / phone affordances. -->
    <dimen name="keyguard_affordance_min_background_radius">30dp</dimen>

    <!-- The grow amount for the camera and phone circles when hinting -->
    <dimen name="hint_grow_amount_sideways">60dp</dimen>

    <!-- The chevron padding to the circle when hinting -->
    <dimen name="hint_chevron_circle_padding">16dp</dimen>

    <!-- Volume panel dialog y offset -->
    <dimen name="volume_panel_top">0dp</dimen>

@@ -317,9 +326,6 @@
    <!-- Move distance for the unlock hint animation on the lockscreen -->
    <dimen name="hint_move_distance">75dp</dimen>

    <!-- Move distance for the other hint animations on the lockscreen (phone, camera)-->
    <dimen name="hint_move_distance_sideways">60dp</dimen>

    <!-- The width of the region on the left/right edge of the screen for performing the camera/
         phone hints. -->
    <dimen name="edge_tap_area_width">48dp</dimen>
+0 −6
Original line number Diff line number Diff line
@@ -244,9 +244,6 @@ public abstract class BaseStatusBar extends SystemUI implements
                            // the user switches to home.  We know it is safe to do at this
                            // point, so make sure new activity switches are now allowed.
                            ActivityManagerNative.getDefault().resumeAppSwitches();
                            // Also, notifications can be launched from the lock screen,
                            // so dismiss the lock screen when the activity starts.
                            ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
                        } catch (RemoteException e) {
                        }

@@ -1153,9 +1150,6 @@ public abstract class BaseStatusBar extends SystemUI implements
                        // the user switches to home.  We know it is safe to do at this
                        // point, so make sure new activity switches are now allowed.
                        ActivityManagerNative.getDefault().resumeAppSwitches();
                        // Also, notifications can be launched from the lock screen,
                        // so dismiss the lock screen when the activity starts.
                        ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
                    } catch (RemoteException e) {
                    }

+429 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2014 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.systemui.statusbar;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ArgbEvaluator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.ImageView;
import com.android.systemui.R;

/**
 * An ImageView which does not have overlapping renderings commands and therefore does not need a
 * layer when alpha is changed.
 */
public class KeyguardAffordanceView extends ImageView {

    private static final long CIRCLE_APPEAR_DURATION = 80;
    private static final long CIRCLE_DISAPPEAR_MAX_DURATION = 200;
    private static final long NORMAL_ANIMATION_DURATION = 200;
    public static final float MAX_ICON_SCALE_AMOUNT = 1.5f;
    public static final float MIN_ICON_SCALE_AMOUNT = 0.8f;

    private final int mMinBackgroundRadius;
    private final Paint mCirclePaint;
    private final Interpolator mAppearInterpolator;
    private final Interpolator mDisappearInterpolator;
    private final int mInverseColor;
    private final int mNormalColor;
    private final ArgbEvaluator mColorInterpolator;
    private final FlingAnimationUtils mFlingAnimationUtils;
    private final Drawable mArrowDrawable;
    private final int mHintChevronPadding;
    private float mCircleRadius;
    private int mCenterX;
    private int mCenterY;
    private ValueAnimator mCircleAnimator;
    private ValueAnimator mAlphaAnimator;
    private ValueAnimator mScaleAnimator;
    private ValueAnimator mArrowAnimator;
    private float mCircleStartValue;
    private boolean mCircleWillBeHidden;
    private int[] mTempPoint = new int[2];
    private float mImageScale;
    private int mCircleColor;
    private boolean mIsLeft;
    private float mArrowAlpha = 0.0f;
    private AnimatorListenerAdapter mCircleEndListener = new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
            mCircleAnimator = null;
        }
    };
    private AnimatorListenerAdapter mScaleEndListener = new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
            mScaleAnimator = null;
        }
    };
    private AnimatorListenerAdapter mAlphaEndListener = new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
            mAlphaAnimator = null;
        }
    };
    private AnimatorListenerAdapter mArrowEndListener = new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
            mArrowAnimator = null;
        }
    };

    public KeyguardAffordanceView(Context context) {
        this(context, null);
    }

    public KeyguardAffordanceView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public KeyguardAffordanceView(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public KeyguardAffordanceView(Context context, AttributeSet attrs, int defStyleAttr,
            int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        mCirclePaint = new Paint();
        mCirclePaint.setAntiAlias(true);
        mCircleColor = 0xffffffff;
        mCirclePaint.setColor(mCircleColor);

        mNormalColor = 0xffffffff;
        mInverseColor = 0xff000000;
        mMinBackgroundRadius = mContext.getResources().getDimensionPixelSize(
                R.dimen.keyguard_affordance_min_background_radius);
        mHintChevronPadding = mContext.getResources().getDimensionPixelSize(
                R.dimen.hint_chevron_circle_padding);
        mAppearInterpolator = AnimationUtils.loadInterpolator(mContext,
                android.R.interpolator.linear_out_slow_in);
        mDisappearInterpolator = AnimationUtils.loadInterpolator(mContext,
                android.R.interpolator.fast_out_linear_in);
        mColorInterpolator = new ArgbEvaluator();
        mFlingAnimationUtils = new FlingAnimationUtils(mContext, 0.3f);
        mArrowDrawable = context.getDrawable(R.drawable.ic_chevron_left);
        mArrowDrawable.setBounds(0, 0, mArrowDrawable.getIntrinsicWidth(),
                mArrowDrawable.getIntrinsicHeight());
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        mCenterX = getWidth() / 2;
        mCenterY = getHeight() / 2;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        drawBackgroundCircle(canvas);
        drawArrow(canvas);
        canvas.save();
        updateIconColor();
        canvas.scale(mImageScale, mImageScale, getWidth() / 2, getHeight() / 2);
        super.onDraw(canvas);
        canvas.restore();
    }

    private void drawArrow(Canvas canvas) {
        if (mArrowAlpha > 0) {
            canvas.save();
            canvas.translate(mCenterX, mCenterY);
            if (mIsLeft) {
                canvas.scale(-1.0f, 1.0f);
            }
            canvas.translate(- mCircleRadius - mHintChevronPadding
                    - mArrowDrawable.getIntrinsicWidth() / 2,
                    - mArrowDrawable.getIntrinsicHeight() / 2);
            mArrowDrawable.setAlpha((int) (mArrowAlpha * 255));
            mArrowDrawable.draw(canvas);
            canvas.restore();
        }
    }

    private void updateIconColor() {
        Drawable drawable = getDrawable().mutate();
        float alpha = mCircleRadius / mMinBackgroundRadius;
        alpha = Math.min(1.0f, alpha);
        int color = (int) mColorInterpolator.evaluate(alpha, mNormalColor, mInverseColor);
        drawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
    }

    private void drawBackgroundCircle(Canvas canvas) {
        if (mCircleRadius > 0) {
            updateCircleColor();
            canvas.drawCircle(mCenterX, mCenterY, mCircleRadius, mCirclePaint);
        }
    }

    private void updateCircleColor() {
        float fraction = 0.5f + 0.5f * Math.max(0.0f, Math.min(1.0f,
                (mCircleRadius - mMinBackgroundRadius) / (0.5f * mMinBackgroundRadius)));
        int color = Color.argb((int) (Color.alpha(mCircleColor) * fraction),
                Color.red(mCircleColor),
                Color.green(mCircleColor), Color.blue(mCircleColor));
        mCirclePaint.setColor(color);
    }

    public void finishAnimation(float velocity, final Runnable mAnimationEndRunnable) {
        cancelAnimator(mCircleAnimator);
        float maxCircleSize = getMaxCircleSize();
        ValueAnimator animatorToRadius = getAnimatorToRadius(maxCircleSize);
        mFlingAnimationUtils.applyDismissing(animatorToRadius, mCircleRadius, maxCircleSize,
                velocity, maxCircleSize);
        animatorToRadius.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                mAnimationEndRunnable.run();
            }
        });
        animatorToRadius.start();
        setImageAlpha(0, true);
    }

    private float getMaxCircleSize() {
        getLocationInWindow(mTempPoint);
        float rootWidth = getRootView().getWidth();
        float width = mTempPoint[0] + mCenterX;
        width = Math.max(rootWidth - width, width);
        float height = mTempPoint[1] + mCenterY;
        return (float) Math.hypot(width, height);
    }

    public void setCircleRadius(float circleRadius) {
        setCircleRadius(circleRadius, false);
    }

    public void setCircleRadiusWithoutAnimation(float circleRadius) {
        cancelAnimator(mCircleAnimator);
        setCircleRadius(circleRadius, true);
    }

    private void setCircleRadius(float circleRadius, boolean noAnimation) {

        // Check if we need a new animation
        boolean radiusHidden = (mCircleAnimator != null && mCircleWillBeHidden)
                || (mCircleAnimator == null && mCircleRadius == 0.0f);
        boolean nowHidden = circleRadius == 0.0f;
        boolean radiusNeedsAnimation = (radiusHidden != nowHidden) && !noAnimation;
        if (!radiusNeedsAnimation) {
            if (mCircleAnimator == null) {
                mCircleRadius = circleRadius;
                invalidate();
            } else if (!mCircleWillBeHidden) {

                // We just update the end value
                float diff = circleRadius - mMinBackgroundRadius;
                PropertyValuesHolder[] values = mCircleAnimator.getValues();
                values[0].setFloatValues(mCircleStartValue + diff, circleRadius);
                mCircleAnimator.setCurrentPlayTime(mCircleAnimator.getCurrentPlayTime());
            }
        } else {
            cancelAnimator(mCircleAnimator);
            ValueAnimator animator = getAnimatorToRadius(circleRadius);
            Interpolator interpolator = circleRadius == 0.0f
                    ? mDisappearInterpolator
                    : mAppearInterpolator;
            animator.setInterpolator(interpolator);
            float durationFactor = Math.abs(mCircleRadius - circleRadius)
                    / (float) mMinBackgroundRadius;
            long duration = (long) (CIRCLE_APPEAR_DURATION * durationFactor);
            duration = Math.min(duration, CIRCLE_DISAPPEAR_MAX_DURATION);
            animator.setDuration(duration);
            animator.start();
        }
    }

    private ValueAnimator getAnimatorToRadius(float circleRadius) {
        ValueAnimator animator = ValueAnimator.ofFloat(mCircleRadius, circleRadius);
        mCircleAnimator = animator;
        mCircleStartValue = mCircleRadius;
        mCircleWillBeHidden = circleRadius == 0.0f;
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mCircleRadius = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        animator.addListener(mCircleEndListener);
        return animator;
    }

    private void cancelAnimator(Animator animator) {
        if (animator != null) {
            animator.cancel();
        }
    }

    public void setImageScale(float imageScale, boolean animate) {
        setImageScale(imageScale, animate, -1, null);
    }

    /**
     * Sets the scale of the containing image
     *
     * @param imageScale The new Scale.
     * @param animate Should an animation be performed
     * @param duration If animate, whats the duration? When -1 we take the default duration
     * @param interpolator If animate, whats the interpolator? When null we take the default
     *                     interpolator.
     */
    public void setImageScale(float imageScale, boolean animate, long duration,
            Interpolator interpolator) {
        cancelAnimator(mScaleAnimator);
        if (!animate) {
            mImageScale = imageScale;
            invalidate();
        } else {
            ValueAnimator animator = ValueAnimator.ofFloat(mImageScale, imageScale);
            mScaleAnimator = animator;
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    mImageScale = (float) animation.getAnimatedValue();
                    invalidate();
                }
            });
            animator.addListener(mScaleEndListener);
            if (interpolator == null) {
                interpolator = imageScale == 0.0f
                        ? mDisappearInterpolator
                        : mAppearInterpolator;
            }
            animator.setInterpolator(interpolator);
            if (duration == -1) {
                float durationFactor = Math.abs(mImageScale - imageScale)
                        / (1.0f - MIN_ICON_SCALE_AMOUNT);
                durationFactor = Math.min(1.0f, durationFactor);
                duration = (long) (NORMAL_ANIMATION_DURATION * durationFactor);
            }
            animator.setDuration(duration);
            animator.start();
        }
    }

    public void setImageAlpha(float alpha, boolean animate) {
        setImageAlpha(alpha, animate, -1, null, null);
    }

    /**
     * Sets the alpha of the containing image
     *
     * @param alpha The new alpha.
     * @param animate Should an animation be performed
     * @param duration If animate, whats the duration? When -1 we take the default duration
     * @param interpolator If animate, whats the interpolator? When null we take the default
     *                     interpolator.
     */
    public void setImageAlpha(float alpha, boolean animate, long duration,
            Interpolator interpolator, Runnable runnable) {
        cancelAnimator(mAlphaAnimator);
        int endAlpha = (int) (alpha * 255);
        if (!animate) {
            setImageAlpha(endAlpha);
        } else {
            int currentAlpha = getImageAlpha();
            ValueAnimator animator = ValueAnimator.ofInt(currentAlpha, endAlpha);
            mAlphaAnimator = animator;
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    setImageAlpha((int) animation.getAnimatedValue());
                }
            });
            animator.addListener(mAlphaEndListener);
            if (interpolator == null) {
                interpolator = alpha == 0.0f
                        ? mDisappearInterpolator
                        : mAppearInterpolator;
            }
            animator.setInterpolator(interpolator);
            if (duration == -1) {
                float durationFactor = Math.abs(currentAlpha - endAlpha) / 255f;
                durationFactor = Math.min(1.0f, durationFactor);
                duration = (long) (NORMAL_ANIMATION_DURATION * durationFactor);
            }
            animator.setDuration(duration);
            if (runnable != null) {
                animator.addListener(getEndListener(runnable));
            }
            animator.start();
        }
    }

    private Animator.AnimatorListener getEndListener(final Runnable runnable) {
        return new AnimatorListenerAdapter() {
            boolean mCancelled;
            @Override
            public void onAnimationCancel(Animator animation) {
                mCancelled = true;
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                if (!mCancelled) {
                    runnable.run();
                }
            }
        };
    }

    public float getCircleRadius() {
        return mCircleRadius;
    }

    public void showArrow(boolean show) {
        cancelAnimator(mArrowAnimator);
        float targetAlpha = show ? 1.0f : 0.0f;
        if (mArrowAlpha == targetAlpha) {
            return;
        }
        ValueAnimator animator = ValueAnimator.ofFloat(mArrowAlpha, targetAlpha);
        mArrowAnimator = animator;
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mArrowAlpha = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        animator.addListener(mArrowEndListener);
        Interpolator interpolator = show
                    ? mAppearInterpolator
                    : mDisappearInterpolator;
        animator.setInterpolator(interpolator);
        float durationFactor = Math.abs(mArrowAlpha - targetAlpha);
        long duration = (long) (NORMAL_ANIMATION_DURATION * durationFactor);
        animator.setDuration(duration);
        animator.start();
    }

    public void setIsLeft(boolean left) {
        mIsLeft = left;
    }
}
Loading