• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.content.res.Configuration.ORIENTATION_LANDSCAPE;
19 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
20 
21 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED;
22 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
23 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
24 
25 import android.annotation.Nullable;
26 import android.content.Context;
27 import android.content.res.Configuration;
28 import android.graphics.Rect;
29 import android.os.SystemClock;
30 import android.text.TextUtils;
31 import android.util.AttributeSet;
32 import android.view.MotionEvent;
33 import android.view.View;
34 import android.view.animation.AnimationUtils;
35 import android.view.animation.Interpolator;
36 
37 import androidx.constraintlayout.motion.widget.MotionLayout;
38 import androidx.constraintlayout.widget.ConstraintLayout;
39 import androidx.constraintlayout.widget.ConstraintSet;
40 
41 import com.android.internal.jank.InteractionJankMonitor;
42 import com.android.internal.widget.LockPatternView;
43 import com.android.settingslib.animation.AppearAnimationCreator;
44 import com.android.settingslib.animation.AppearAnimationUtils;
45 import com.android.settingslib.animation.DisappearAnimationUtils;
46 import com.android.systemui.Flags;
47 import com.android.systemui.bouncer.shared.constants.PatternBouncerConstants.ColorId;
48 import com.android.systemui.res.R;
49 import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostureInt;
50 
51 public class KeyguardPatternView extends KeyguardInputView
52         implements AppearAnimationCreator<LockPatternView.CellState> {
53 
54     private static final String TAG = "SecurityPatternView";
55     private static final boolean DEBUG = KeyguardConstants.DEBUG;
56 
57 
58     // how long we stay awake after each key beyond MIN_PATTERN_BEFORE_POKE_WAKELOCK
59     private static final int UNLOCK_PATTERN_WAKE_INTERVAL_MS = 7000;
60 
61     // How much we scale up the duration of the disappear animation when the current user is locked
62     public static final float DISAPPEAR_MULTIPLIER_LOCKED = 1.5f;
63 
64     // Extra padding, in pixels, that should eat touch events.
65     private static final int PATTERNS_TOUCH_AREA_EXTENSION = 40;
66 
67     private final AppearAnimationUtils mAppearAnimationUtils;
68     private final DisappearAnimationUtils mDisappearAnimationUtils;
69     private final DisappearAnimationUtils mDisappearAnimationUtilsLocked;
70     private final int[] mTmpPosition = new int[2];
71     private final Rect mTempRect = new Rect();
72     private final Rect mLockPatternScreenBounds = new Rect();
73 
74     private LockPatternView mLockPatternView;
75 
76     /**
77      * Keeps track of the last time we poked the wake lock during dispatching of the touch event.
78      * Initialized to something guaranteed to make us poke the wakelock when the user starts
79      * drawing the pattern.
80      * @see #dispatchTouchEvent(android.view.MotionEvent)
81      */
82     private long mLastPokeTime = -UNLOCK_PATTERN_WAKE_INTERVAL_MS;
83 
84     BouncerKeyguardMessageArea mSecurityMessageDisplay;
85     private View mEcaView;
86     @Nullable private MotionLayout mContainerMotionLayout;
87     // TODO (b/293252410) - usage of mContainerConstraintLayout should be removed
88     //  when the flag is enabled/removed
89     @Nullable private ConstraintLayout mContainerConstraintLayout;
90     private boolean mAlreadyUsingSplitBouncer = false;
91     private boolean mIsSmallLockScreenLandscapeEnabled = false;
92     @DevicePostureInt private int mLastDevicePosture = DEVICE_POSTURE_UNKNOWN;
93 
KeyguardPatternView(Context context)94     public KeyguardPatternView(Context context) {
95         this(context, null);
96     }
97 
KeyguardPatternView(Context context, AttributeSet attrs)98     public KeyguardPatternView(Context context, AttributeSet attrs) {
99         super(context, attrs);
100         mAppearAnimationUtils = new AppearAnimationUtils(context,
101                 AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 1.5f /* translationScale */,
102                 2.0f /* delayScale */, AnimationUtils.loadInterpolator(
103                         mContext, android.R.interpolator.linear_out_slow_in));
104         mDisappearAnimationUtils = new DisappearAnimationUtils(context,
105                 125, 1.2f /* translationScale */,
106                 0.6f /* delayScale */, AnimationUtils.loadInterpolator(
107                         mContext, android.R.interpolator.fast_out_linear_in));
108         mDisappearAnimationUtilsLocked = new DisappearAnimationUtils(context,
109                 (long) (125 * DISAPPEAR_MULTIPLIER_LOCKED), 1.2f /* translationScale */,
110                 0.6f /* delayScale */, AnimationUtils.loadInterpolator(
111                 mContext, android.R.interpolator.fast_out_linear_in));
112     }
113 
114     /**
115      * Use motion layout (new bouncer implementation) if LOCKSCREEN_ENABLE_LANDSCAPE flag is
116      * enabled, instead of constraint layout (old bouncer implementation)
117      */
setIsLockScreenLandscapeEnabled(boolean isLockScreenLandscapeEnabled)118     public void setIsLockScreenLandscapeEnabled(boolean isLockScreenLandscapeEnabled) {
119         mIsSmallLockScreenLandscapeEnabled = isLockScreenLandscapeEnabled;
120         findContainerLayout();
121     }
122 
findContainerLayout()123     private void findContainerLayout() {
124         if (mIsSmallLockScreenLandscapeEnabled) {
125             mContainerMotionLayout = findViewById(R.id.pattern_container);
126         } else {
127             mContainerConstraintLayout = findViewById(R.id.pattern_container);
128         }
129     }
130 
131     @Override
onConfigurationChanged(Configuration newConfig)132     protected void onConfigurationChanged(Configuration newConfig) {
133         updateMargins();
134     }
135 
onDevicePostureChanged(@evicePostureInt int posture)136     void onDevicePostureChanged(@DevicePostureInt int posture) {
137         if (mLastDevicePosture == posture) return;
138         mLastDevicePosture = posture;
139 
140         if (mIsSmallLockScreenLandscapeEnabled) {
141             boolean useSplitBouncerAfterFold =
142                     mLastDevicePosture == DEVICE_POSTURE_CLOSED
143                     && getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE
144                     && getResources().getBoolean(R.bool.update_bouncer_constraints);
145 
146             if (mAlreadyUsingSplitBouncer != useSplitBouncerAfterFold) {
147                 updateConstraints(useSplitBouncerAfterFold);
148             }
149         }
150 
151         updateMargins();
152     }
153 
updateMargins()154     private void updateMargins() {
155         if (mIsSmallLockScreenLandscapeEnabled) {
156             updateHalfFoldedConstraints();
157         } else {
158             updateHalfFoldedGuideline();
159         }
160     }
161 
updateHalfFoldedConstraints()162     private void updateHalfFoldedConstraints() {
163         // Update the constraints based on the device posture...
164         if (mAlreadyUsingSplitBouncer) return;
165 
166         boolean shouldCollapsePattern =
167                 mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED
168                         && mContext.getResources().getConfiguration().orientation
169                         == ORIENTATION_PORTRAIT;
170 
171         int expectedMotionLayoutState = shouldCollapsePattern
172                 ? R.id.half_folded_single_constraints
173                 : R.id.single_constraints;
174 
175         transitionToMotionLayoutState(expectedMotionLayoutState);
176     }
177 
178     // TODO (b/293252410) - this method can be removed when the flag is enabled/removed
updateHalfFoldedGuideline()179     private void updateHalfFoldedGuideline() {
180         // Update the guideline based on the device posture...
181         float halfOpenPercentage =
182                 mContext.getResources().getFloat(R.dimen.half_opened_bouncer_height_ratio);
183 
184         ConstraintSet cs = new ConstraintSet();
185         cs.clone(mContainerConstraintLayout);
186         cs.setGuidelinePercent(R.id.pattern_top_guideline,
187                 mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED ? halfOpenPercentage : 0.0f);
188         cs.applyTo(mContainerConstraintLayout);
189     }
190 
transitionToMotionLayoutState(int state)191     private void transitionToMotionLayoutState(int state) {
192         if (mContainerMotionLayout.getCurrentState() != state) {
193             mContainerMotionLayout.transitionToState(state);
194         }
195     }
196 
197     /**
198      * Updates the keyguard view's constraints (single or split constraints).
199      * Split constraints are only used for small landscape screens.
200      * Only called when flag LANDSCAPE_ENABLE_LOCKSCREEN is enabled.
201      */
202     @Override
updateConstraints(boolean useSplitBouncer)203     protected void updateConstraints(boolean useSplitBouncer) {
204         if (!mIsSmallLockScreenLandscapeEnabled) return;
205 
206         mAlreadyUsingSplitBouncer = useSplitBouncer;
207 
208         if (useSplitBouncer) {
209             mContainerMotionLayout.jumpToState(R.id.split_constraints);
210             mContainerMotionLayout.setMaxWidth(Integer.MAX_VALUE);
211         } else {
212             boolean useHalfFoldedConstraints =
213                     mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED
214                             && mContext.getResources().getConfiguration().orientation
215                             == ORIENTATION_PORTRAIT;
216 
217             if (useHalfFoldedConstraints) {
218                 mContainerMotionLayout.jumpToState(R.id.half_folded_single_constraints);
219             } else {
220                 mContainerMotionLayout.jumpToState(R.id.single_constraints);
221             }
222             mContainerMotionLayout.setMaxWidth(getResources()
223                     .getDimensionPixelSize(R.dimen.biometric_auth_pattern_view_max_size));
224         }
225     }
226 
227     @Override
onFinishInflate()228     protected void onFinishInflate() {
229         super.onFinishInflate();
230 
231         mLockPatternView = findViewById(R.id.lockPatternView);
232         if (Flags.bouncerUiRevamp2()) {
233             mLockPatternView.setDotColors(mContext.getColor(ColorId.dotColor), mContext.getColor(
234                     ColorId.activatedDotColor));
235             mLockPatternView.setColors(mContext.getColor(ColorId.pathColor), 0, 0);
236             mLockPatternView.setDotSizes(
237                     getResources().getDimensionPixelSize(R.dimen.keyguard_pattern_dot_size),
238                     getResources().getDimensionPixelSize(
239                             R.dimen.keyguard_pattern_activated_dot_size));
240             mLockPatternView.setPathWidth(
241                     getResources().getDimensionPixelSize(R.dimen.keyguard_pattern_stroke_width));
242         }
243 
244         mEcaView = findViewById(R.id.keyguard_selector_fade_container);
245     }
246 
247     @Override
onAttachedToWindow()248     protected void onAttachedToWindow() {
249         super.onAttachedToWindow();
250         mSecurityMessageDisplay = findViewById(R.id.bouncer_message_area);
251     }
252 
253     @Override
onTouchEvent(MotionEvent ev)254     public boolean onTouchEvent(MotionEvent ev) {
255         boolean result = super.onTouchEvent(ev);
256         // as long as the user is entering a pattern (i.e sending a touch event that was handled
257         // by this screen), keep poking the wake lock so that the screen will stay on.
258         final long elapsed = SystemClock.elapsedRealtime() - mLastPokeTime;
259         if (result && (elapsed > (UNLOCK_PATTERN_WAKE_INTERVAL_MS - 100))) {
260             mLastPokeTime = SystemClock.elapsedRealtime();
261         }
262         mTempRect.set(0, 0, 0, 0);
263         offsetRectIntoDescendantCoords(mLockPatternView, mTempRect);
264         ev.offsetLocation(mTempRect.left, mTempRect.top);
265         result = mLockPatternView.dispatchTouchEvent(ev) || result;
266         ev.offsetLocation(-mTempRect.left, -mTempRect.top);
267         return result;
268     }
269 
270     @Override
onLayout(boolean changed, int l, int t, int r, int b)271     protected void onLayout(boolean changed, int l, int t, int r, int b) {
272         super.onLayout(changed, l, t, r, b);
273         mLockPatternView.getLocationOnScreen(mTmpPosition);
274         mLockPatternScreenBounds.set(mTmpPosition[0] - PATTERNS_TOUCH_AREA_EXTENSION,
275                 mTmpPosition[1] - PATTERNS_TOUCH_AREA_EXTENSION,
276                 mTmpPosition[0] + mLockPatternView.getWidth() + PATTERNS_TOUCH_AREA_EXTENSION,
277                 mTmpPosition[1] + mLockPatternView.getHeight() + PATTERNS_TOUCH_AREA_EXTENSION);
278     }
279 
280     @Override
disallowInterceptTouch(MotionEvent event)281     boolean disallowInterceptTouch(MotionEvent event) {
282         return !mLockPatternView.isEmpty()
283                 || mLockPatternScreenBounds.contains((int) event.getRawX(), (int) event.getRawY());
284     }
285 
startAppearAnimation()286     public void startAppearAnimation() {
287         enableClipping(false);
288         setAlpha(0f);
289         setTranslationY(mAppearAnimationUtils.getStartTranslation());
290         AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 500 /* duration */,
291                 0, mAppearAnimationUtils.getInterpolator(),
292                 getAnimationListener(InteractionJankMonitor.CUJ_LOCKSCREEN_PATTERN_APPEAR));
293         mLockPatternView.post(() -> {
294             setAlpha(1f);
295             mAppearAnimationUtils.startAnimation2d(
296                     mLockPatternView.getCellStates(),
297                     () -> {
298                         enableClipping(true);
299                         mLockPatternView.invalidate();
300                     },
301                     KeyguardPatternView.this);
302         });
303         if (!TextUtils.isEmpty(mSecurityMessageDisplay.getText())) {
304             mAppearAnimationUtils.createAnimation(mSecurityMessageDisplay, 0,
305                     AppearAnimationUtils.DEFAULT_APPEAR_DURATION,
306                     mAppearAnimationUtils.getStartTranslation(),
307                     true /* appearing */,
308                     mAppearAnimationUtils.getInterpolator(),
309                     null /* finishRunnable */);
310         }
311     }
312 
startDisappearAnimation(boolean needsSlowUnlockTransition, final Runnable finishRunnable)313     public boolean startDisappearAnimation(boolean needsSlowUnlockTransition,
314             final Runnable finishRunnable) {
315         float durationMultiplier = needsSlowUnlockTransition ? DISAPPEAR_MULTIPLIER_LOCKED : 1f;
316         mLockPatternView.clearPattern();
317         enableClipping(false);
318         setTranslationY(0);
319         AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */,
320                 (long) (300 * durationMultiplier),
321                 -mDisappearAnimationUtils.getStartTranslation(),
322                 mDisappearAnimationUtils.getInterpolator(),
323                 getAnimationListener(InteractionJankMonitor.CUJ_LOCKSCREEN_PATTERN_DISAPPEAR));
324 
325         DisappearAnimationUtils disappearAnimationUtils = needsSlowUnlockTransition
326                 ? mDisappearAnimationUtilsLocked : mDisappearAnimationUtils;
327         disappearAnimationUtils.startAnimation2d(mLockPatternView.getCellStates(),
328                 () -> {
329                     enableClipping(true);
330                     if (finishRunnable != null) {
331                         finishRunnable.run();
332                     }
333                 }, KeyguardPatternView.this);
334         if (!TextUtils.isEmpty(mSecurityMessageDisplay.getText())) {
335             mDisappearAnimationUtils.createAnimation(mSecurityMessageDisplay, 0,
336                     (long) (200 * durationMultiplier),
337                     -mDisappearAnimationUtils.getStartTranslation() * 3,
338                     false /* appearing */,
339                     mDisappearAnimationUtils.getInterpolator(),
340                     null /* finishRunnable */);
341         }
342         return true;
343     }
344 
enableClipping(boolean enable)345     private void enableClipping(boolean enable) {
346         if (mContainerConstraintLayout != null) {
347             setClipChildren(enable);
348             mContainerConstraintLayout.setClipToPadding(enable);
349             mContainerConstraintLayout.setClipChildren(enable);
350         }
351         if (mContainerMotionLayout != null) {
352             setClipChildren(enable);
353             mContainerMotionLayout.setClipToPadding(enable);
354             mContainerMotionLayout.setClipChildren(enable);
355         }
356     }
357 
358     @Override
createAnimation(final LockPatternView.CellState animatedCell, long delay, long duration, float translationY, final boolean appearing, Interpolator interpolator, final Runnable finishListener)359     public void createAnimation(final LockPatternView.CellState animatedCell, long delay,
360             long duration, float translationY, final boolean appearing,
361             Interpolator interpolator,
362             final Runnable finishListener) {
363         mLockPatternView.startCellStateAnimation(animatedCell,
364                 1f, appearing ? 1f : 0f, /* alpha */
365                 appearing ? translationY : 0f, appearing ? 0f : translationY, /* translation */
366                 appearing ? 0f : 1f, 1f /* scale */,
367                 delay, duration, interpolator, finishListener);
368         if (finishListener != null) {
369             // Also animate the Emergency call
370             mAppearAnimationUtils.createAnimation(mEcaView, delay, duration, translationY,
371                     appearing, interpolator, null);
372         }
373     }
374 
375     @Override
hasOverlappingRendering()376     public boolean hasOverlappingRendering() {
377         return false;
378     }
379 
380     @Override
getTitle()381     public CharSequence getTitle() {
382         return getResources().getString(
383                 com.android.internal.R.string.keyguard_accessibility_pattern_unlock);
384     }
385 }
386