| /* |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.keyguard; |
| |
| import android.content.Context; |
| import android.graphics.Rect; |
| import android.os.SystemClock; |
| import android.text.TextUtils; |
| import android.util.AttributeSet; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.animation.AnimationUtils; |
| import android.view.animation.Interpolator; |
| |
| import com.android.internal.jank.InteractionJankMonitor; |
| import com.android.internal.widget.LockPatternView; |
| import com.android.settingslib.animation.AppearAnimationCreator; |
| import com.android.settingslib.animation.AppearAnimationUtils; |
| import com.android.settingslib.animation.DisappearAnimationUtils; |
| import com.android.systemui.R; |
| |
| public class KeyguardPatternView extends KeyguardInputView |
| implements AppearAnimationCreator<LockPatternView.CellState> { |
| |
| private static final String TAG = "SecurityPatternView"; |
| private static final boolean DEBUG = KeyguardConstants.DEBUG; |
| |
| |
| // how long we stay awake after each key beyond MIN_PATTERN_BEFORE_POKE_WAKELOCK |
| private static final int UNLOCK_PATTERN_WAKE_INTERVAL_MS = 7000; |
| |
| // How much we scale up the duration of the disappear animation when the current user is locked |
| public static final float DISAPPEAR_MULTIPLIER_LOCKED = 1.5f; |
| |
| // Extra padding, in pixels, that should eat touch events. |
| private static final int PATTERNS_TOUCH_AREA_EXTENSION = 40; |
| |
| private final AppearAnimationUtils mAppearAnimationUtils; |
| private final DisappearAnimationUtils mDisappearAnimationUtils; |
| private final DisappearAnimationUtils mDisappearAnimationUtilsLocked; |
| private final int[] mTmpPosition = new int[2]; |
| private final Rect mTempRect = new Rect(); |
| private final Rect mLockPatternScreenBounds = new Rect(); |
| |
| private LockPatternView mLockPatternView; |
| |
| /** |
| * Keeps track of the last time we poked the wake lock during dispatching of the touch event. |
| * Initialized to something guaranteed to make us poke the wakelock when the user starts |
| * drawing the pattern. |
| * @see #dispatchTouchEvent(android.view.MotionEvent) |
| */ |
| private long mLastPokeTime = -UNLOCK_PATTERN_WAKE_INTERVAL_MS; |
| |
| KeyguardMessageArea mSecurityMessageDisplay; |
| private View mEcaView; |
| private ViewGroup mContainer; |
| |
| public KeyguardPatternView(Context context) { |
| this(context, null); |
| } |
| |
| public KeyguardPatternView(Context context, AttributeSet attrs) { |
| super(context, attrs); |
| mAppearAnimationUtils = new AppearAnimationUtils(context, |
| AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 1.5f /* translationScale */, |
| 2.0f /* delayScale */, AnimationUtils.loadInterpolator( |
| mContext, android.R.interpolator.linear_out_slow_in)); |
| mDisappearAnimationUtils = new DisappearAnimationUtils(context, |
| 125, 1.2f /* translationScale */, |
| 0.6f /* delayScale */, AnimationUtils.loadInterpolator( |
| mContext, android.R.interpolator.fast_out_linear_in)); |
| mDisappearAnimationUtilsLocked = new DisappearAnimationUtils(context, |
| (long) (125 * DISAPPEAR_MULTIPLIER_LOCKED), 1.2f /* translationScale */, |
| 0.6f /* delayScale */, AnimationUtils.loadInterpolator( |
| mContext, android.R.interpolator.fast_out_linear_in)); |
| } |
| |
| @Override |
| protected void onFinishInflate() { |
| super.onFinishInflate(); |
| |
| mLockPatternView = findViewById(R.id.lockPatternView); |
| |
| mEcaView = findViewById(R.id.keyguard_selector_fade_container); |
| mContainer = findViewById(R.id.pattern_container); |
| } |
| |
| @Override |
| protected void onAttachedToWindow() { |
| super.onAttachedToWindow(); |
| mSecurityMessageDisplay = KeyguardMessageArea.findSecurityMessageDisplay(this); |
| } |
| |
| @Override |
| public boolean onTouchEvent(MotionEvent ev) { |
| boolean result = super.onTouchEvent(ev); |
| // as long as the user is entering a pattern (i.e sending a touch event that was handled |
| // by this screen), keep poking the wake lock so that the screen will stay on. |
| final long elapsed = SystemClock.elapsedRealtime() - mLastPokeTime; |
| if (result && (elapsed > (UNLOCK_PATTERN_WAKE_INTERVAL_MS - 100))) { |
| mLastPokeTime = SystemClock.elapsedRealtime(); |
| } |
| mTempRect.set(0, 0, 0, 0); |
| offsetRectIntoDescendantCoords(mLockPatternView, mTempRect); |
| ev.offsetLocation(mTempRect.left, mTempRect.top); |
| result = mLockPatternView.dispatchTouchEvent(ev) || result; |
| ev.offsetLocation(-mTempRect.left, -mTempRect.top); |
| return result; |
| } |
| |
| @Override |
| protected void onLayout(boolean changed, int l, int t, int r, int b) { |
| super.onLayout(changed, l, t, r, b); |
| mLockPatternView.getLocationOnScreen(mTmpPosition); |
| mLockPatternScreenBounds.set(mTmpPosition[0] - PATTERNS_TOUCH_AREA_EXTENSION, |
| mTmpPosition[1] - PATTERNS_TOUCH_AREA_EXTENSION, |
| mTmpPosition[0] + mLockPatternView.getWidth() + PATTERNS_TOUCH_AREA_EXTENSION, |
| mTmpPosition[1] + mLockPatternView.getHeight() + PATTERNS_TOUCH_AREA_EXTENSION); |
| } |
| |
| @Override |
| boolean disallowInterceptTouch(MotionEvent event) { |
| return !mLockPatternView.isEmpty() |
| || mLockPatternScreenBounds.contains((int) event.getRawX(), (int) event.getRawY()); |
| } |
| |
| public void startAppearAnimation() { |
| enableClipping(false); |
| setAlpha(1f); |
| setTranslationY(mAppearAnimationUtils.getStartTranslation()); |
| AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 500 /* duration */, |
| 0, mAppearAnimationUtils.getInterpolator(), |
| getAnimationListener(InteractionJankMonitor.CUJ_LOCKSCREEN_PATTERN_APPEAR)); |
| mAppearAnimationUtils.startAnimation2d( |
| mLockPatternView.getCellStates(), |
| () -> enableClipping(true), |
| this); |
| if (!TextUtils.isEmpty(mSecurityMessageDisplay.getText())) { |
| mAppearAnimationUtils.createAnimation(mSecurityMessageDisplay, 0, |
| AppearAnimationUtils.DEFAULT_APPEAR_DURATION, |
| mAppearAnimationUtils.getStartTranslation(), |
| true /* appearing */, |
| mAppearAnimationUtils.getInterpolator(), |
| null /* finishRunnable */); |
| } |
| } |
| |
| public boolean startDisappearAnimation(boolean needsSlowUnlockTransition, |
| final Runnable finishRunnable) { |
| float durationMultiplier = needsSlowUnlockTransition ? DISAPPEAR_MULTIPLIER_LOCKED : 1f; |
| mLockPatternView.clearPattern(); |
| enableClipping(false); |
| setTranslationY(0); |
| AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, |
| (long) (300 * durationMultiplier), |
| -mDisappearAnimationUtils.getStartTranslation(), |
| mDisappearAnimationUtils.getInterpolator(), |
| getAnimationListener(InteractionJankMonitor.CUJ_LOCKSCREEN_PATTERN_DISAPPEAR)); |
| |
| DisappearAnimationUtils disappearAnimationUtils = needsSlowUnlockTransition |
| ? mDisappearAnimationUtilsLocked : mDisappearAnimationUtils; |
| disappearAnimationUtils.startAnimation2d(mLockPatternView.getCellStates(), |
| () -> { |
| enableClipping(true); |
| if (finishRunnable != null) { |
| finishRunnable.run(); |
| } |
| }, KeyguardPatternView.this); |
| if (!TextUtils.isEmpty(mSecurityMessageDisplay.getText())) { |
| mDisappearAnimationUtils.createAnimation(mSecurityMessageDisplay, 0, |
| (long) (200 * durationMultiplier), |
| - mDisappearAnimationUtils.getStartTranslation() * 3, |
| false /* appearing */, |
| mDisappearAnimationUtils.getInterpolator(), |
| null /* finishRunnable */); |
| } |
| return true; |
| } |
| |
| private void enableClipping(boolean enable) { |
| setClipChildren(enable); |
| mContainer.setClipToPadding(enable); |
| mContainer.setClipChildren(enable); |
| } |
| |
| @Override |
| public void createAnimation(final LockPatternView.CellState animatedCell, long delay, |
| long duration, float translationY, final boolean appearing, |
| Interpolator interpolator, |
| final Runnable finishListener) { |
| mLockPatternView.startCellStateAnimation(animatedCell, |
| 1f, appearing ? 1f : 0f, /* alpha */ |
| appearing ? translationY : 0f, appearing ? 0f : translationY, /* translation */ |
| appearing ? 0f : 1f, 1f /* scale */, |
| delay, duration, interpolator, finishListener); |
| if (finishListener != null) { |
| // Also animate the Emergency call |
| mAppearAnimationUtils.createAnimation(mEcaView, delay, duration, translationY, |
| appearing, interpolator, null); |
| } |
| } |
| |
| @Override |
| public boolean hasOverlappingRendering() { |
| return false; |
| } |
| |
| @Override |
| public CharSequence getTitle() { |
| return getResources().getString( |
| com.android.internal.R.string.keyguard_accessibility_pattern_unlock); |
| } |
| } |