1 /* 2 * Copyright (C) 2019 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 android.animation.AnimatorSet; 20 import android.animation.ValueAnimator; 21 import android.annotation.IntDef; 22 import android.content.Context; 23 import android.graphics.Outline; 24 import android.util.Log; 25 import android.view.View; 26 import android.view.ViewOutlineProvider; 27 import android.view.animation.AccelerateDecelerateInterpolator; 28 29 import com.android.systemui.R; 30 31 import java.lang.annotation.Retention; 32 import java.lang.annotation.RetentionPolicy; 33 34 /** 35 * Controls the back panel and its animations for the BiometricPrompt UI. 36 */ 37 public class AuthPanelController extends ViewOutlineProvider { 38 public static final int POSITION_BOTTOM = 1; 39 public static final int POSITION_LEFT = 2; 40 public static final int POSITION_RIGHT = 3; 41 42 @IntDef({POSITION_BOTTOM, POSITION_LEFT, POSITION_RIGHT}) 43 @Retention(RetentionPolicy.SOURCE) 44 public @interface Position {} 45 46 private static final String TAG = "BiometricPrompt/AuthPanelController"; 47 private static final boolean DEBUG = false; 48 49 private final Context mContext; 50 private final View mPanelView; 51 52 @Position private int mPosition = POSITION_BOTTOM; 53 private boolean mUseFullScreen; 54 55 private int mContainerWidth; 56 private int mContainerHeight; 57 58 private int mContentWidth; 59 private int mContentHeight; 60 61 private float mCornerRadius; 62 private int mMargin; 63 64 @Override getOutline(View view, Outline outline)65 public void getOutline(View view, Outline outline) { 66 final int left = getLeftBound(mPosition); 67 final int right = left + mContentWidth; 68 69 // If the content fits in the container, shrink the height to wrap it. Otherwise, expand to 70 // fill the display (minus the margin), since the content is scrollable. 71 final int top = getTopBound(mPosition); 72 final int bottom = Math.min(top + mContentHeight, mContainerHeight - mMargin); 73 74 outline.setRoundRect(left, top, right, bottom, mCornerRadius); 75 } 76 getLeftBound(@osition int position)77 private int getLeftBound(@Position int position) { 78 switch (position) { 79 case POSITION_BOTTOM: 80 return (mContainerWidth - mContentWidth) / 2; 81 case POSITION_LEFT: 82 return mMargin; 83 case POSITION_RIGHT: 84 return mContainerWidth - mContentWidth - mMargin; 85 default: 86 Log.e(TAG, "Unrecognized position: " + position); 87 return getLeftBound(POSITION_BOTTOM); 88 } 89 } 90 getTopBound(@osition int position)91 private int getTopBound(@Position int position) { 92 switch (position) { 93 case POSITION_BOTTOM: 94 return Math.max(mContainerHeight - mContentHeight - mMargin, mMargin); 95 case POSITION_LEFT: 96 case POSITION_RIGHT: 97 return Math.max((mContainerHeight - mContentHeight) / 2, mMargin); 98 default: 99 Log.e(TAG, "Unrecognized position: " + position); 100 return getTopBound(POSITION_BOTTOM); 101 } 102 } 103 setContainerDimensions(int containerWidth, int containerHeight)104 public void setContainerDimensions(int containerWidth, int containerHeight) { 105 if (DEBUG) { 106 Log.v(TAG, "Container Width: " + containerWidth + " Height: " + containerHeight); 107 } 108 mContainerWidth = containerWidth; 109 mContainerHeight = containerHeight; 110 } 111 setPosition(@osition int position)112 public void setPosition(@Position int position) { 113 mPosition = position; 114 } 115 setUseFullScreen(boolean fullScreen)116 public void setUseFullScreen(boolean fullScreen) { 117 mUseFullScreen = fullScreen; 118 } 119 getTranslationAnimator(float relativeTranslationY)120 public ValueAnimator getTranslationAnimator(float relativeTranslationY) { 121 final ValueAnimator animator = ValueAnimator.ofFloat( 122 mPanelView.getY(), mPanelView.getY() - relativeTranslationY); 123 animator.addUpdateListener(animation -> { 124 final float translation = (float) animation.getAnimatedValue(); 125 mPanelView.setTranslationY(translation); 126 }); 127 return animator; 128 } 129 getAlphaAnimator(float alpha)130 public ValueAnimator getAlphaAnimator(float alpha) { 131 final ValueAnimator animator = ValueAnimator.ofFloat(mPanelView.getAlpha(), alpha); 132 animator.addUpdateListener(animation -> { 133 mPanelView.setAlpha((float) animation.getAnimatedValue()); 134 }); 135 return animator; 136 } 137 updateForContentDimensions(int contentWidth, int contentHeight, int animateDurationMs)138 public void updateForContentDimensions(int contentWidth, int contentHeight, 139 int animateDurationMs) { 140 if (DEBUG) { 141 Log.v(TAG, "Content Width: " + contentWidth 142 + " Height: " + contentHeight 143 + " Animate: " + animateDurationMs); 144 } 145 146 if (mContainerWidth == 0 || mContainerHeight == 0) { 147 Log.w(TAG, "Not done measuring yet"); 148 return; 149 } 150 151 final int margin = mUseFullScreen ? 0 : (int) mContext.getResources() 152 .getDimension(R.dimen.biometric_dialog_border_padding); 153 final float cornerRadius = mUseFullScreen ? 0 : mContext.getResources() 154 .getDimension(R.dimen.biometric_dialog_corner_size); 155 156 if (animateDurationMs > 0) { 157 // Animate margin 158 ValueAnimator marginAnimator = ValueAnimator.ofInt(mMargin, margin); 159 marginAnimator.addUpdateListener((animation) -> { 160 mMargin = (int) animation.getAnimatedValue(); 161 }); 162 163 // Animate corners 164 ValueAnimator cornerAnimator = ValueAnimator.ofFloat(mCornerRadius, cornerRadius); 165 cornerAnimator.addUpdateListener((animation) -> { 166 mCornerRadius = (float) animation.getAnimatedValue(); 167 }); 168 169 // Animate height 170 ValueAnimator heightAnimator = ValueAnimator.ofInt(mContentHeight, contentHeight); 171 heightAnimator.addUpdateListener((animation) -> { 172 mContentHeight = (int) animation.getAnimatedValue(); 173 mPanelView.invalidateOutline(); 174 }); 175 176 // Animate width 177 ValueAnimator widthAnimator = ValueAnimator.ofInt(mContentWidth, contentWidth); 178 widthAnimator.addUpdateListener((animation) -> { 179 mContentWidth = (int) animation.getAnimatedValue(); 180 }); 181 182 // Play together 183 AnimatorSet as = new AnimatorSet(); 184 as.setDuration(animateDurationMs); 185 as.setInterpolator(new AccelerateDecelerateInterpolator()); 186 as.playTogether(cornerAnimator, heightAnimator, widthAnimator, marginAnimator); 187 as.start(); 188 189 } else { 190 mMargin = margin; 191 mCornerRadius = cornerRadius; 192 mContentWidth = contentWidth; 193 mContentHeight = contentHeight; 194 mPanelView.invalidateOutline(); 195 } 196 } 197 getContainerWidth()198 int getContainerWidth() { 199 return mContainerWidth; 200 } 201 getContainerHeight()202 int getContainerHeight() { 203 return mContainerHeight; 204 } 205 AuthPanelController(Context context, View panelView)206 AuthPanelController(Context context, View panelView) { 207 mContext = context; 208 mPanelView = panelView; 209 mCornerRadius = context.getResources() 210 .getDimension(R.dimen.biometric_dialog_corner_size); 211 mMargin = (int) context.getResources() 212 .getDimension(R.dimen.biometric_dialog_border_padding); 213 mPanelView.setOutlineProvider(this); 214 mPanelView.setClipToOutline(true); 215 } 216 217 } 218