• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.graphics.RectF;
30 import android.util.AttributeSet;
31 import android.util.MathUtils;
32 import android.view.View;
33 import android.view.ViewGroup;
34 import android.widget.ImageView;
35 
36 import androidx.annotation.IntDef;
37 import androidx.annotation.Nullable;
38 import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
39 
40 import com.android.settingslib.Utils;
41 import com.android.systemui.R;
42 import com.android.systemui.animation.Interpolators;
43 
44 import com.airbnb.lottie.LottieAnimationView;
45 import com.airbnb.lottie.LottieProperty;
46 import com.airbnb.lottie.model.KeyPath;
47 
48 import java.io.PrintWriter;
49 import java.lang.annotation.Retention;
50 import java.lang.annotation.RetentionPolicy;
51 
52 /**
53  * View corresponding with udfps_keyguard_view.xml
54  */
55 public class UdfpsKeyguardView extends UdfpsAnimationView {
56     private UdfpsDrawable mFingerprintDrawable; // placeholder
57     private LottieAnimationView mAodFp;
58     private LottieAnimationView mLockScreenFp;
59 
60     // used when highlighting fp icon:
61     private int mTextColorPrimary;
62     private ImageView mBgProtection;
63     boolean mUdfpsRequested;
64 
65     private AnimatorSet mBackgroundInAnimator = new AnimatorSet();
66     private int mAlpha; // 0-255
67     private float mScaleFactor = 1;
68 
69     // AOD anti-burn-in offsets
70     private final int mMaxBurnInOffsetX;
71     private final int mMaxBurnInOffsetY;
72     private float mBurnInOffsetX;
73     private float mBurnInOffsetY;
74     private float mBurnInProgress;
75     private float mInterpolatedDarkAmount;
76     private int mAnimationType = ANIMATION_NONE;
77     private boolean mFullyInflated;
78 
79     private LayoutParams mParams;
80 
UdfpsKeyguardView(Context context, @Nullable AttributeSet attrs)81     public UdfpsKeyguardView(Context context, @Nullable AttributeSet attrs) {
82         super(context, attrs);
83         mFingerprintDrawable = new UdfpsFpDrawable(context);
84 
85         mMaxBurnInOffsetX = context.getResources()
86             .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x);
87         mMaxBurnInOffsetY = context.getResources()
88             .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
89     }
90 
91     @Override
onFinishInflate()92     protected void onFinishInflate() {
93         super.onFinishInflate();
94 
95         // inflate Lottie views on a background thread in case it takes a while to inflate
96         AsyncLayoutInflater inflater = new AsyncLayoutInflater(mContext);
97         inflater.inflate(R.layout.udfps_keyguard_view_internal, this,
98                 mLayoutInflaterFinishListener);
99     }
100 
101     @Override
getDrawable()102     public UdfpsDrawable getDrawable() {
103         return mFingerprintDrawable;
104     }
105 
106     @Override
onDisplayConfiguring()107     void onDisplayConfiguring() {
108     }
109 
110     @Override
onDisplayUnconfigured()111     void onDisplayUnconfigured() {
112     }
113 
114     @Override
dozeTimeTick()115     public boolean dozeTimeTick() {
116         updateBurnInOffsets();
117         return true;
118     }
119 
updateBurnInOffsets()120     private void updateBurnInOffsets() {
121         if (!mFullyInflated) {
122             return;
123         }
124 
125         // if we're animating from screen off, we can immediately place the icon in the
126         // AoD-burn in location, else we need to translate the icon from LS => AoD.
127         final float darkAmountForAnimation = mAnimationType == ANIMATION_UNLOCKED_SCREEN_OFF
128                 ? 1f : mInterpolatedDarkAmount;
129         mBurnInOffsetX = MathUtils.lerp(0f,
130             getBurnInOffset(mMaxBurnInOffsetX * 2, true /* xAxis */)
131                 - mMaxBurnInOffsetX, darkAmountForAnimation);
132         mBurnInOffsetY = MathUtils.lerp(0f,
133             getBurnInOffset(mMaxBurnInOffsetY * 2, false /* xAxis */)
134                 - mMaxBurnInOffsetY, darkAmountForAnimation);
135         mBurnInProgress = MathUtils.lerp(0f, getBurnInProgressOffset(), darkAmountForAnimation);
136 
137         if (mAnimationType == ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN && !mPauseAuth) {
138             mLockScreenFp.setTranslationX(mBurnInOffsetX);
139             mLockScreenFp.setTranslationY(mBurnInOffsetY);
140             mBgProtection.setAlpha(1f - mInterpolatedDarkAmount);
141             mLockScreenFp.setAlpha(1f - mInterpolatedDarkAmount);
142         } else if (darkAmountForAnimation == 0f) {
143             mLockScreenFp.setTranslationX(0);
144             mLockScreenFp.setTranslationY(0);
145             mBgProtection.setAlpha(mAlpha / 255f);
146             mLockScreenFp.setAlpha(mAlpha / 255f);
147         } else {
148             mBgProtection.setAlpha(0f);
149             mLockScreenFp.setAlpha(0f);
150         }
151         mLockScreenFp.setProgress(1f - mInterpolatedDarkAmount);
152 
153         mAodFp.setTranslationX(mBurnInOffsetX);
154         mAodFp.setTranslationY(mBurnInOffsetY);
155         mAodFp.setProgress(mBurnInProgress);
156         mAodFp.setAlpha(mInterpolatedDarkAmount);
157 
158         // done animating
159         final boolean doneAnimatingBetweenAodAndLS =
160                 mAnimationType == ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN
161                         && (mInterpolatedDarkAmount == 0f || mInterpolatedDarkAmount == 1f);
162         final boolean doneAnimatingUnlockedScreenOff =
163                 mAnimationType == ANIMATION_UNLOCKED_SCREEN_OFF
164                         && (mInterpolatedDarkAmount == 1f);
165         if (doneAnimatingBetweenAodAndLS || doneAnimatingUnlockedScreenOff) {
166             mAnimationType = ANIMATION_NONE;
167         }
168     }
169 
requestUdfps(boolean request, int color)170     void requestUdfps(boolean request, int color) {
171         mUdfpsRequested = request;
172     }
173 
updateColor()174     void updateColor() {
175         if (!mFullyInflated) {
176             return;
177         }
178 
179         mTextColorPrimary = Utils.getColorAttrDefaultColor(mContext,
180             android.R.attr.textColorPrimary);
181         mBgProtection.setImageDrawable(getContext().getDrawable(R.drawable.fingerprint_bg));
182         mLockScreenFp.invalidate(); // updated with a valueCallback
183     }
184 
setScaleFactor(float scale)185     void setScaleFactor(float scale) {
186         mScaleFactor = scale;
187     }
188 
updatePadding()189     void updatePadding() {
190         if (mLockScreenFp == null || mAodFp == null) {
191             return;
192         }
193 
194         final int defaultPaddingPx =
195                 getResources().getDimensionPixelSize(R.dimen.lock_icon_padding);
196         final int padding = (int) (defaultPaddingPx * mScaleFactor);
197         mLockScreenFp.setPadding(padding, padding, padding, padding);
198         mAodFp.setPadding(padding, padding, padding, padding);
199     }
200 
201     /**
202      * @param alpha between 0 and 255
203      */
setUnpausedAlpha(int alpha)204     void setUnpausedAlpha(int alpha) {
205         mAlpha = alpha;
206         updateAlpha();
207     }
208 
209     /**
210      * @return alpha between 0 and 255
211      */
getUnpausedAlpha()212     int getUnpausedAlpha() {
213         return mAlpha;
214     }
215 
216     @Override
updateAlpha()217     protected int updateAlpha() {
218         int alpha = super.updateAlpha();
219         updateBurnInOffsets();
220         return alpha;
221     }
222 
223     @Override
calculateAlpha()224     int calculateAlpha() {
225         if (mPauseAuth) {
226             return 0;
227         }
228         return mAlpha;
229     }
230 
231     static final int ANIMATION_NONE = 0;
232     static final int ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN = 1;
233     static final int ANIMATION_UNLOCKED_SCREEN_OFF = 2;
234 
235     @Retention(RetentionPolicy.SOURCE)
236     @IntDef({ANIMATION_NONE, ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN, ANIMATION_UNLOCKED_SCREEN_OFF})
237     private @interface AnimationType {}
238 
onDozeAmountChanged(float linear, float eased, @AnimationType int animationType)239     void onDozeAmountChanged(float linear, float eased, @AnimationType int animationType) {
240         mAnimationType = animationType;
241         mInterpolatedDarkAmount = eased;
242         updateAlpha();
243     }
244 
245     @Override
onSensorRectUpdated(RectF bounds)246     void onSensorRectUpdated(RectF bounds) {
247         super.onSensorRectUpdated(bounds);
248 
249         if (mUseExpandedOverlay) {
250             mParams = new LayoutParams((int) bounds.width(), (int) bounds.height());
251             RectF converted = getBoundsRelativeToView(bounds);
252             mParams.setMargins(
253                     (int) converted.left,
254                     (int) converted.top,
255                     (int) converted.right,
256                     (int) converted.bottom
257             );
258         }
259     }
260 
261     /**
262      * Animates in the bg protection circle behind the fp icon to highlight the icon.
263      */
animateInUdfpsBouncer(Runnable onEndAnimation)264     void animateInUdfpsBouncer(Runnable onEndAnimation) {
265         if (mBackgroundInAnimator.isRunning() || !mFullyInflated) {
266             // already animating in or not yet inflated
267             return;
268         }
269 
270         // fade in and scale up
271         mBackgroundInAnimator = new AnimatorSet();
272         mBackgroundInAnimator.playTogether(
273                 ObjectAnimator.ofFloat(mBgProtection, View.ALPHA, 0f, 1f),
274                 ObjectAnimator.ofFloat(mBgProtection, View.SCALE_X, 0f, 1f),
275                 ObjectAnimator.ofFloat(mBgProtection, View.SCALE_Y, 0f, 1f));
276         mBackgroundInAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
277         mBackgroundInAnimator.setDuration(500);
278         mBackgroundInAnimator.addListener(new AnimatorListenerAdapter() {
279             @Override
280             public void onAnimationEnd(Animator animation) {
281                 if (onEndAnimation != null) {
282                     onEndAnimation.run();
283                 }
284             }
285         });
286         mBackgroundInAnimator.start();
287     }
288 
289     /**
290      * Print debugging information for this class.
291      */
dump(PrintWriter pw)292     public void dump(PrintWriter pw) {
293         pw.println("UdfpsKeyguardView (" + this + ")");
294         pw.println("    mPauseAuth=" + mPauseAuth);
295         pw.println("    mUnpausedAlpha=" + getUnpausedAlpha());
296         pw.println("    mUdfpsRequested=" + mUdfpsRequested);
297         pw.println("    mInterpolatedDarkAmount=" + mInterpolatedDarkAmount);
298         pw.println("    mAnimationType=" + mAnimationType);
299         pw.println("    mUseExpandedOverlay=" + mUseExpandedOverlay);
300     }
301 
302     private final AsyncLayoutInflater.OnInflateFinishedListener mLayoutInflaterFinishListener =
303             new AsyncLayoutInflater.OnInflateFinishedListener() {
304         @Override
305         public void onInflateFinished(View view, int resid, ViewGroup parent) {
306             mFullyInflated = true;
307             mAodFp = view.findViewById(R.id.udfps_aod_fp);
308             mLockScreenFp = view.findViewById(R.id.udfps_lockscreen_fp);
309             mBgProtection = view.findViewById(R.id.udfps_keyguard_fp_bg);
310 
311             updatePadding();
312             updateColor();
313             updateAlpha();
314 
315             if (mUseExpandedOverlay) {
316                 parent.addView(view, mParams);
317             } else {
318                 parent.addView(view);
319             }
320 
321             // requires call to invalidate to update the color
322             mLockScreenFp.addValueCallback(
323                     new KeyPath("**"), LottieProperty.COLOR_FILTER,
324                     frameInfo -> new PorterDuffColorFilter(mTextColorPrimary,
325                             PorterDuff.Mode.SRC_ATOP)
326             );
327         }
328     };
329 }
330