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