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