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