1 /*
2  * Copyright (C) 2012 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 package com.android.keyguard;
17 
18 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
19 
20 import android.content.Context;
21 import android.graphics.Rect;
22 import android.os.SystemClock;
23 import android.text.TextUtils;
24 import android.util.AttributeSet;
25 import android.view.MotionEvent;
26 import android.view.View;
27 import android.view.animation.AnimationUtils;
28 import android.view.animation.Interpolator;
29 
30 import androidx.constraintlayout.widget.ConstraintLayout;
31 import androidx.constraintlayout.widget.ConstraintSet;
32 
33 import com.android.internal.jank.InteractionJankMonitor;
34 import com.android.internal.widget.LockPatternView;
35 import com.android.settingslib.animation.AppearAnimationCreator;
36 import com.android.settingslib.animation.AppearAnimationUtils;
37 import com.android.settingslib.animation.DisappearAnimationUtils;
38 import com.android.systemui.R;
39 import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostureInt;
40 
41 public class KeyguardPatternView extends KeyguardInputView
42         implements AppearAnimationCreator<LockPatternView.CellState> {
43 
44     private static final String TAG = "SecurityPatternView";
45     private static final boolean DEBUG = KeyguardConstants.DEBUG;
46 
47 
48     // how long we stay awake after each key beyond MIN_PATTERN_BEFORE_POKE_WAKELOCK
49     private static final int UNLOCK_PATTERN_WAKE_INTERVAL_MS = 7000;
50 
51     // How much we scale up the duration of the disappear animation when the current user is locked
52     public static final float DISAPPEAR_MULTIPLIER_LOCKED = 1.5f;
53 
54     // Extra padding, in pixels, that should eat touch events.
55     private static final int PATTERNS_TOUCH_AREA_EXTENSION = 40;
56 
57     private final AppearAnimationUtils mAppearAnimationUtils;
58     private final DisappearAnimationUtils mDisappearAnimationUtils;
59     private final DisappearAnimationUtils mDisappearAnimationUtilsLocked;
60     private final int[] mTmpPosition = new int[2];
61     private final Rect mTempRect = new Rect();
62     private final Rect mLockPatternScreenBounds = new Rect();
63 
64     private LockPatternView mLockPatternView;
65 
66     /**
67      * Keeps track of the last time we poked the wake lock during dispatching of the touch event.
68      * Initialized to something guaranteed to make us poke the wakelock when the user starts
69      * drawing the pattern.
70      * @see #dispatchTouchEvent(android.view.MotionEvent)
71      */
72     private long mLastPokeTime = -UNLOCK_PATTERN_WAKE_INTERVAL_MS;
73 
74     KeyguardMessageArea mSecurityMessageDisplay;
75     private View mEcaView;
76     private ConstraintLayout mContainer;
77 
KeyguardPatternView(Context context)78     public KeyguardPatternView(Context context) {
79         this(context, null);
80     }
81 
KeyguardPatternView(Context context, AttributeSet attrs)82     public KeyguardPatternView(Context context, AttributeSet attrs) {
83         super(context, attrs);
84         mAppearAnimationUtils = new AppearAnimationUtils(context,
85                 AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 1.5f /* translationScale */,
86                 2.0f /* delayScale */, AnimationUtils.loadInterpolator(
87                         mContext, android.R.interpolator.linear_out_slow_in));
88         mDisappearAnimationUtils = new DisappearAnimationUtils(context,
89                 125, 1.2f /* translationScale */,
90                 0.6f /* delayScale */, AnimationUtils.loadInterpolator(
91                         mContext, android.R.interpolator.fast_out_linear_in));
92         mDisappearAnimationUtilsLocked = new DisappearAnimationUtils(context,
93                 (long) (125 * DISAPPEAR_MULTIPLIER_LOCKED), 1.2f /* translationScale */,
94                 0.6f /* delayScale */, AnimationUtils.loadInterpolator(
95                 mContext, android.R.interpolator.fast_out_linear_in));
96     }
97 
onDevicePostureChanged(@evicePostureInt int posture)98     void onDevicePostureChanged(@DevicePostureInt int posture) {
99         // Update the guideline based on the device posture...
100         float halfOpenPercentage =
101                 mContext.getResources().getFloat(R.dimen.half_opened_bouncer_height_ratio);
102 
103         ConstraintSet cs = new ConstraintSet();
104         cs.clone(mContainer);
105         cs.setGuidelinePercent(R.id.pattern_top_guideline, posture == DEVICE_POSTURE_HALF_OPENED
106                 ? halfOpenPercentage : 0.0f);
107         cs.applyTo(mContainer);
108     }
109 
110     @Override
onFinishInflate()111     protected void onFinishInflate() {
112         super.onFinishInflate();
113 
114         mLockPatternView = findViewById(R.id.lockPatternView);
115 
116         mEcaView = findViewById(R.id.keyguard_selector_fade_container);
117         mContainer = findViewById(R.id.pattern_container);
118     }
119 
120     @Override
onAttachedToWindow()121     protected void onAttachedToWindow() {
122         super.onAttachedToWindow();
123         mSecurityMessageDisplay = KeyguardMessageArea.findSecurityMessageDisplay(this);
124     }
125 
126     @Override
onTouchEvent(MotionEvent ev)127     public boolean onTouchEvent(MotionEvent ev) {
128         boolean result = super.onTouchEvent(ev);
129         // as long as the user is entering a pattern (i.e sending a touch event that was handled
130         // by this screen), keep poking the wake lock so that the screen will stay on.
131         final long elapsed = SystemClock.elapsedRealtime() - mLastPokeTime;
132         if (result && (elapsed > (UNLOCK_PATTERN_WAKE_INTERVAL_MS - 100))) {
133             mLastPokeTime = SystemClock.elapsedRealtime();
134         }
135         mTempRect.set(0, 0, 0, 0);
136         offsetRectIntoDescendantCoords(mLockPatternView, mTempRect);
137         ev.offsetLocation(mTempRect.left, mTempRect.top);
138         result = mLockPatternView.dispatchTouchEvent(ev) || result;
139         ev.offsetLocation(-mTempRect.left, -mTempRect.top);
140         return result;
141     }
142 
143     @Override
onLayout(boolean changed, int l, int t, int r, int b)144     protected void onLayout(boolean changed, int l, int t, int r, int b) {
145         super.onLayout(changed, l, t, r, b);
146         mLockPatternView.getLocationOnScreen(mTmpPosition);
147         mLockPatternScreenBounds.set(mTmpPosition[0] - PATTERNS_TOUCH_AREA_EXTENSION,
148                 mTmpPosition[1] - PATTERNS_TOUCH_AREA_EXTENSION,
149                 mTmpPosition[0] + mLockPatternView.getWidth() + PATTERNS_TOUCH_AREA_EXTENSION,
150                 mTmpPosition[1] + mLockPatternView.getHeight() + PATTERNS_TOUCH_AREA_EXTENSION);
151     }
152 
153     @Override
disallowInterceptTouch(MotionEvent event)154     boolean disallowInterceptTouch(MotionEvent event) {
155         return !mLockPatternView.isEmpty()
156                 || mLockPatternScreenBounds.contains((int) event.getRawX(), (int) event.getRawY());
157     }
158 
startAppearAnimation()159     public void startAppearAnimation() {
160         enableClipping(false);
161         setAlpha(1f);
162         setTranslationY(mAppearAnimationUtils.getStartTranslation());
163         AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 500 /* duration */,
164                 0, mAppearAnimationUtils.getInterpolator(),
165                 getAnimationListener(InteractionJankMonitor.CUJ_LOCKSCREEN_PATTERN_APPEAR));
166         mAppearAnimationUtils.startAnimation2d(
167                 mLockPatternView.getCellStates(),
168                 () -> enableClipping(true),
169                 this);
170         if (!TextUtils.isEmpty(mSecurityMessageDisplay.getText())) {
171             mAppearAnimationUtils.createAnimation(mSecurityMessageDisplay, 0,
172                     AppearAnimationUtils.DEFAULT_APPEAR_DURATION,
173                     mAppearAnimationUtils.getStartTranslation(),
174                     true /* appearing */,
175                     mAppearAnimationUtils.getInterpolator(),
176                     null /* finishRunnable */);
177         }
178     }
179 
startDisappearAnimation(boolean needsSlowUnlockTransition, final Runnable finishRunnable)180     public boolean startDisappearAnimation(boolean needsSlowUnlockTransition,
181             final Runnable finishRunnable) {
182         float durationMultiplier = needsSlowUnlockTransition ? DISAPPEAR_MULTIPLIER_LOCKED : 1f;
183         mLockPatternView.clearPattern();
184         enableClipping(false);
185         setTranslationY(0);
186         AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */,
187                 (long) (300 * durationMultiplier),
188                 -mDisappearAnimationUtils.getStartTranslation(),
189                 mDisappearAnimationUtils.getInterpolator(),
190                 getAnimationListener(InteractionJankMonitor.CUJ_LOCKSCREEN_PATTERN_DISAPPEAR));
191 
192         DisappearAnimationUtils disappearAnimationUtils = needsSlowUnlockTransition
193                         ? mDisappearAnimationUtilsLocked : mDisappearAnimationUtils;
194         disappearAnimationUtils.startAnimation2d(mLockPatternView.getCellStates(),
195                 () -> {
196                     enableClipping(true);
197                     if (finishRunnable != null) {
198                         finishRunnable.run();
199                     }
200                 }, KeyguardPatternView.this);
201         if (!TextUtils.isEmpty(mSecurityMessageDisplay.getText())) {
202             mDisappearAnimationUtils.createAnimation(mSecurityMessageDisplay, 0,
203                     (long) (200 * durationMultiplier),
204                     - mDisappearAnimationUtils.getStartTranslation() * 3,
205                     false /* appearing */,
206                     mDisappearAnimationUtils.getInterpolator(),
207                     null /* finishRunnable */);
208         }
209         return true;
210     }
211 
enableClipping(boolean enable)212     private void enableClipping(boolean enable) {
213         setClipChildren(enable);
214         mContainer.setClipToPadding(enable);
215         mContainer.setClipChildren(enable);
216     }
217 
218     @Override
createAnimation(final LockPatternView.CellState animatedCell, long delay, long duration, float translationY, final boolean appearing, Interpolator interpolator, final Runnable finishListener)219     public void createAnimation(final LockPatternView.CellState animatedCell, long delay,
220             long duration, float translationY, final boolean appearing,
221             Interpolator interpolator,
222             final Runnable finishListener) {
223         mLockPatternView.startCellStateAnimation(animatedCell,
224                 1f, appearing ? 1f : 0f, /* alpha */
225                 appearing ? translationY : 0f, appearing ? 0f : translationY, /* translation */
226                 appearing ? 0f : 1f, 1f /* scale */,
227                 delay, duration, interpolator, finishListener);
228         if (finishListener != null) {
229             // Also animate the Emergency call
230             mAppearAnimationUtils.createAnimation(mEcaView, delay, duration, translationY,
231                     appearing, interpolator, null);
232         }
233     }
234 
235     @Override
hasOverlappingRendering()236     public boolean hasOverlappingRendering() {
237         return false;
238     }
239 
240     @Override
getTitle()241     public CharSequence getTitle() {
242         return getResources().getString(
243                 com.android.internal.R.string.keyguard_accessibility_pattern_unlock);
244     }
245 }
246