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 updateForContentDimensions(int contentWidth, int contentHeight, int animateDurationMs)120 public void updateForContentDimensions(int contentWidth, int contentHeight, 121 int animateDurationMs) { 122 if (DEBUG) { 123 Log.v(TAG, "Content Width: " + contentWidth 124 + " Height: " + contentHeight 125 + " Animate: " + animateDurationMs); 126 } 127 128 if (mContainerWidth == 0 || mContainerHeight == 0) { 129 Log.w(TAG, "Not done measuring yet"); 130 return; 131 } 132 133 final int margin = mUseFullScreen ? 0 : (int) mContext.getResources() 134 .getDimension(R.dimen.biometric_dialog_border_padding); 135 final float cornerRadius = mUseFullScreen ? 0 : mContext.getResources() 136 .getDimension(R.dimen.biometric_dialog_corner_size); 137 138 if (animateDurationMs > 0) { 139 // Animate margin 140 ValueAnimator marginAnimator = ValueAnimator.ofInt(mMargin, margin); 141 marginAnimator.addUpdateListener((animation) -> { 142 mMargin = (int) animation.getAnimatedValue(); 143 }); 144 145 // Animate corners 146 ValueAnimator cornerAnimator = ValueAnimator.ofFloat(mCornerRadius, cornerRadius); 147 cornerAnimator.addUpdateListener((animation) -> { 148 mCornerRadius = (float) animation.getAnimatedValue(); 149 }); 150 151 // Animate height 152 ValueAnimator heightAnimator = ValueAnimator.ofInt(mContentHeight, contentHeight); 153 heightAnimator.addUpdateListener((animation) -> { 154 mContentHeight = (int) animation.getAnimatedValue(); 155 mPanelView.invalidateOutline(); 156 }); 157 158 // Animate width 159 ValueAnimator widthAnimator = ValueAnimator.ofInt(mContentWidth, contentWidth); 160 widthAnimator.addUpdateListener((animation) -> { 161 mContentWidth = (int) animation.getAnimatedValue(); 162 }); 163 164 // Play together 165 AnimatorSet as = new AnimatorSet(); 166 as.setDuration(animateDurationMs); 167 as.setInterpolator(new AccelerateDecelerateInterpolator()); 168 as.playTogether(cornerAnimator, heightAnimator, widthAnimator, marginAnimator); 169 as.start(); 170 171 } else { 172 mMargin = margin; 173 mCornerRadius = cornerRadius; 174 mContentWidth = contentWidth; 175 mContentHeight = contentHeight; 176 mPanelView.invalidateOutline(); 177 } 178 } 179 getContainerWidth()180 int getContainerWidth() { 181 return mContainerWidth; 182 } 183 getContainerHeight()184 int getContainerHeight() { 185 return mContainerHeight; 186 } 187 AuthPanelController(Context context, View panelView)188 AuthPanelController(Context context, View panelView) { 189 mContext = context; 190 mPanelView = panelView; 191 mCornerRadius = context.getResources() 192 .getDimension(R.dimen.biometric_dialog_corner_size); 193 mMargin = (int) context.getResources() 194 .getDimension(R.dimen.biometric_dialog_border_padding); 195 mPanelView.setOutlineProvider(this); 196 mPanelView.setClipToOutline(true); 197 } 198 199 } 200