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