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