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