1 /*
2  * Copyright (C) 2021 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.biometrics;
18 
19 import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
20 import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInProgressOffset;
21 
22 import android.animation.Animator;
23 import android.animation.AnimatorListenerAdapter;
24 import android.animation.AnimatorSet;
25 import android.animation.ObjectAnimator;
26 import android.content.Context;
27 import android.graphics.PorterDuff;
28 import android.graphics.PorterDuffColorFilter;
29 import android.util.AttributeSet;
30 import android.util.MathUtils;
31 import android.view.View;
32 import android.widget.ImageView;
33 
34 import androidx.annotation.Nullable;
35 
36 import com.android.settingslib.Utils;
37 import com.android.systemui.R;
38 import com.android.systemui.animation.Interpolators;
39 import com.android.systemui.statusbar.StatusBarState;
40 
41 import com.airbnb.lottie.LottieAnimationView;
42 import com.airbnb.lottie.LottieProperty;
43 import com.airbnb.lottie.model.KeyPath;
44 
45 /**
46  * View corresponding with udfps_keyguard_view.xml
47  */
48 public class UdfpsKeyguardView extends UdfpsAnimationView {
49     private UdfpsDrawable mFingerprintDrawable; // placeholder
50     private LottieAnimationView mAodFp;
51     private LottieAnimationView mLockScreenFp;
52     private int mStatusBarState;
53 
54     // used when highlighting fp icon:
55     private int mTextColorPrimary;
56     private ImageView mBgProtection;
57     boolean mUdfpsRequested;
58 
59     private AnimatorSet mBackgroundInAnimator = new AnimatorSet();
60     private int mAlpha; // 0-255
61 
62     // AOD anti-burn-in offsets
63     private final int mMaxBurnInOffsetX;
64     private final int mMaxBurnInOffsetY;
65     private float mBurnInOffsetX;
66     private float mBurnInOffsetY;
67     private float mBurnInProgress;
68     private float mInterpolatedDarkAmount;
69 
UdfpsKeyguardView(Context context, @Nullable AttributeSet attrs)70     public UdfpsKeyguardView(Context context, @Nullable AttributeSet attrs) {
71         super(context, attrs);
72         mFingerprintDrawable = new UdfpsFpDrawable(context);
73 
74         mMaxBurnInOffsetX = context.getResources()
75             .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x);
76         mMaxBurnInOffsetY = context.getResources()
77             .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
78     }
79 
80     @Override
onFinishInflate()81     protected void onFinishInflate() {
82         super.onFinishInflate();
83         mAodFp = findViewById(R.id.udfps_aod_fp);
84         mLockScreenFp = findViewById(R.id.udfps_lockscreen_fp);
85         mBgProtection = findViewById(R.id.udfps_keyguard_fp_bg);
86 
87         updateColor();
88 
89         // requires call to invalidate to update the color
90         mLockScreenFp.addValueCallback(
91                 new KeyPath("**"), LottieProperty.COLOR_FILTER,
92                 frameInfo -> new PorterDuffColorFilter(mTextColorPrimary, PorterDuff.Mode.SRC_ATOP)
93         );
94     }
95 
96     @Override
getDrawable()97     public UdfpsDrawable getDrawable() {
98         return mFingerprintDrawable;
99     }
100 
101     @Override
onIlluminationStarting()102     void onIlluminationStarting() {
103     }
104 
105     @Override
onIlluminationStopped()106     void onIlluminationStopped() {
107     }
108 
109     @Override
dozeTimeTick()110     public boolean dozeTimeTick() {
111         updateBurnInOffsets();
112         return true;
113     }
114 
updateBurnInOffsets()115     private void updateBurnInOffsets() {
116         mBurnInOffsetX = MathUtils.lerp(0f,
117             getBurnInOffset(mMaxBurnInOffsetX * 2, true /* xAxis */)
118                 - mMaxBurnInOffsetX, mInterpolatedDarkAmount);
119         mBurnInOffsetY = MathUtils.lerp(0f,
120             getBurnInOffset(mMaxBurnInOffsetY * 2, false /* xAxis */)
121                 - mMaxBurnInOffsetY, mInterpolatedDarkAmount);
122         mBurnInProgress = MathUtils.lerp(0f, getBurnInProgressOffset(), mInterpolatedDarkAmount);
123 
124         mAodFp.setTranslationX(mBurnInOffsetX);
125         mAodFp.setTranslationY(mBurnInOffsetY);
126         mAodFp.setProgress(mBurnInProgress);
127         mAodFp.setAlpha(255 * mInterpolatedDarkAmount);
128 
129         mLockScreenFp.setTranslationX(mBurnInOffsetX);
130         mLockScreenFp.setTranslationY(mBurnInOffsetY);
131         mLockScreenFp.setProgress(1f - mInterpolatedDarkAmount);
132         mLockScreenFp.setAlpha((1f - mInterpolatedDarkAmount) * 255);
133     }
134 
requestUdfps(boolean request, int color)135     void requestUdfps(boolean request, int color) {
136         mUdfpsRequested = request;
137     }
138 
setStatusBarState(int statusBarState)139     void setStatusBarState(int statusBarState) {
140         mStatusBarState = statusBarState;
141     }
142 
updateColor()143     void updateColor() {
144         mTextColorPrimary = Utils.getColorAttrDefaultColor(mContext,
145             android.R.attr.textColorPrimary);
146         mBgProtection.setImageDrawable(getContext().getDrawable(R.drawable.fingerprint_bg));
147         mLockScreenFp.invalidate(); // updated with a valueCallback
148     }
149 
150     /**
151      * @param alpha between 0 and 255
152      */
setUnpausedAlpha(int alpha)153     void setUnpausedAlpha(int alpha) {
154         mAlpha = alpha;
155         updateAlpha();
156     }
157 
158     /**
159      * @return alpha between 0 and 255
160      */
getUnpausedAlpha()161     int getUnpausedAlpha() {
162         return mAlpha;
163     }
164 
165     @Override
updateAlpha()166     protected int updateAlpha() {
167         int alpha = super.updateAlpha();
168         mLockScreenFp.setAlpha(alpha / 255f);
169         if (mInterpolatedDarkAmount != 0f) {
170             mBgProtection.setAlpha(1f - mInterpolatedDarkAmount);
171         } else {
172             mBgProtection.setAlpha(alpha / 255f);
173         }
174 
175         return alpha;
176     }
177 
178     @Override
calculateAlpha()179     int calculateAlpha() {
180         if (mPauseAuth) {
181             return 0;
182         }
183         return mAlpha;
184     }
185 
onDozeAmountChanged(float linear, float eased)186     void onDozeAmountChanged(float linear, float eased) {
187         mInterpolatedDarkAmount = eased;
188         updateAlpha();
189         updateBurnInOffsets();
190     }
191 
192     /**
193      * Animates in the bg protection circle behind the fp icon to highlight the icon.
194      */
animateInUdfpsBouncer(Runnable onEndAnimation)195     void animateInUdfpsBouncer(Runnable onEndAnimation) {
196         if (mBackgroundInAnimator.isRunning()) {
197             // already animating in
198             return;
199         }
200 
201         // fade in and scale up
202         mBackgroundInAnimator = new AnimatorSet();
203         mBackgroundInAnimator.playTogether(
204                 ObjectAnimator.ofFloat(mBgProtection, View.ALPHA, 0f, 1f),
205                 ObjectAnimator.ofFloat(mBgProtection, View.SCALE_X, 0f, 1f),
206                 ObjectAnimator.ofFloat(mBgProtection, View.SCALE_Y, 0f, 1f));
207         mBackgroundInAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
208         mBackgroundInAnimator.setDuration(500);
209         mBackgroundInAnimator.addListener(new AnimatorListenerAdapter() {
210             @Override
211             public void onAnimationEnd(Animator animation) {
212                 if (onEndAnimation != null) {
213                     onEndAnimation.run();
214                 }
215             }
216         });
217         mBackgroundInAnimator.start();
218     }
219 
isShadeLocked()220     private boolean isShadeLocked() {
221         return mStatusBarState == StatusBarState.SHADE_LOCKED;
222     }
223 }
224