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