blob: 22182677babeeccf800108b1fa7c20699c3e0b1e [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2007 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
17package com.android.internal.widget;
18
Jorim Jaggic1581972014-08-07 22:15:48 +020019import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ValueAnimator;
Andrei Onea15884392019-03-22 17:28:11 +000022import android.annotation.UnsupportedAppUsage;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.content.Context;
Jim Miller240a2952015-06-10 16:55:24 -070024import android.content.res.Resources;
Jim Millerbf1259b2010-03-31 18:15:27 -070025import android.content.res.TypedArray;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026import android.graphics.Canvas;
Jorim Jaggi613f55f2015-07-16 15:30:10 -070027import android.graphics.CanvasProperty;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.graphics.Paint;
29import android.graphics.Path;
John Reck32f140aa62018-10-04 15:08:24 -070030import android.graphics.RecordingCanvas;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031import android.graphics.Rect;
John Reck32f140aa62018-10-04 15:08:24 -070032import android.graphics.drawable.Drawable;
Jim Miller240a2952015-06-10 16:55:24 -070033import android.media.AudioManager;
34import android.os.Bundle;
Karl Rosaen6a109b42009-07-10 16:08:56 -070035import android.os.Debug;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080036import android.os.Parcel;
37import android.os.Parcelable;
38import android.os.SystemClock;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039import android.util.AttributeSet;
Jim Miller240a2952015-06-10 16:55:24 -070040import android.util.IntArray;
41import android.util.Log;
Phil Weaverf5d677d2017-05-30 17:26:59 -070042import android.util.SparseArray;
Jim Milleraef555b2011-10-12 16:41:30 -070043import android.view.HapticFeedbackConstants;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080044import android.view.MotionEvent;
Jorim Jaggi613f55f2015-07-16 15:30:10 -070045import android.view.RenderNodeAnimator;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080046import android.view.View;
Jim Miller240a2952015-06-10 16:55:24 -070047import android.view.accessibility.AccessibilityEvent;
Svetoslav Ganov530d9f12011-10-04 14:51:25 -070048import android.view.accessibility.AccessibilityManager;
Jim Miller240a2952015-06-10 16:55:24 -070049import android.view.accessibility.AccessibilityNodeInfo;
50import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
Jorim Jaggic1581972014-08-07 22:15:48 +020051import android.view.animation.AnimationUtils;
52import android.view.animation.Interpolator;
Svetoslav Ganov530d9f12011-10-04 14:51:25 -070053
54import com.android.internal.R;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080055
56import java.util.ArrayList;
57import java.util.List;
58
59/**
60 * Displays and detects the user's unlock attempt, which is a drag of a finger
61 * across 9 regions of the screen.
62 *
63 * Is also capable of displaying a static pattern in "in progress", "wrong" or
64 * "correct" states.
65 */
66public class LockPatternView extends View {
Jim Millerbf1259b2010-03-31 18:15:27 -070067 // Aspect to use when rendering this view
68 private static final int ASPECT_SQUARE = 0; // View will be the minimum of width/height
69 private static final int ASPECT_LOCK_WIDTH = 1; // Fixed width; height will be minimum of (w,h)
70 private static final int ASPECT_LOCK_HEIGHT = 2; // Fixed height; width will be minimum of (w,h)
71
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080072 private static final boolean PROFILE_DRAWING = false;
Lucas Dupin31c00222018-11-05 18:13:54 -080073 private static final float LINE_FADE_ALPHA_MULTIPLIER = 1.5f;
Selim Cinek30181972014-05-30 03:33:06 +020074 private final CellState[][] mCellStates;
Jorim Jaggic1581972014-08-07 22:15:48 +020075
76 private final int mDotSize;
77 private final int mDotSizeActivated;
78 private final int mPathWidth;
79
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080080 private boolean mDrawingProfilingStarted = false;
81
Andrei Onea15884392019-03-22 17:28:11 +000082 @UnsupportedAppUsage
Paul Crowleya97227a2014-10-23 10:25:41 +010083 private final Paint mPaint = new Paint();
Andrei Onea15884392019-03-22 17:28:11 +000084 @UnsupportedAppUsage
Paul Crowleya97227a2014-10-23 10:25:41 +010085 private final Paint mPathPaint = new Paint();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080086
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080087 /**
88 * How many milliseconds we spend animating each circle of a lock pattern
89 * if the animating mode is set. The entire animation should take this
90 * constant * the length of the pattern to complete.
91 */
92 private static final int MILLIS_PER_CIRCLE_ANIMATING = 700;
93
Jim Miller0caa3772013-03-07 18:44:32 -080094 /**
95 * This can be used to avoid updating the display for very small motions or noisy panels.
96 * It didn't seem to have much impact on the devices tested, so currently set to 0.
97 */
98 private static final float DRAG_THRESHHOLD = 0.0f;
Jim Miller240a2952015-06-10 16:55:24 -070099 public static final int VIRTUAL_BASE_VIEW_ID = 1;
Adrian Roosd2def942015-07-27 13:49:53 -0700100 public static final boolean DEBUG_A11Y = false;
Jim Miller240a2952015-06-10 16:55:24 -0700101 private static final String TAG = "LockPatternView";
Jim Miller0caa3772013-03-07 18:44:32 -0800102
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800103 private OnPatternListener mOnPatternListener;
Andrei Onea15884392019-03-22 17:28:11 +0000104 @UnsupportedAppUsage
Paul Crowleya97227a2014-10-23 10:25:41 +0100105 private final ArrayList<Cell> mPattern = new ArrayList<Cell>(9);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800106
107 /**
108 * Lookup table for the circles of the pattern we are currently drawing.
109 * This will be the cells of the complete pattern unless we are animating,
110 * in which case we use this to hold the cells we are drawing for the in
111 * progress animation.
112 */
Paul Crowleya97227a2014-10-23 10:25:41 +0100113 private final boolean[][] mPatternDrawLookup = new boolean[3][3];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800114
115 /**
116 * the in progress point:
117 * - during interaction: where the user's finger is
118 * - during animation: the current tip of the animating line
119 */
120 private float mInProgressX = -1;
121 private float mInProgressY = -1;
122
123 private long mAnimatingPeriodStart;
Vishwath Mohandcc1f1942018-01-24 16:54:25 -0800124 private long[] mLineFadeStart = new long[9];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800125
Andrei Onea15884392019-03-22 17:28:11 +0000126 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800127 private DisplayMode mPatternDisplayMode = DisplayMode.Correct;
128 private boolean mInputEnabled = true;
Andrei Onea15884392019-03-22 17:28:11 +0000129 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800130 private boolean mInStealthMode = false;
Jim Milleraef555b2011-10-12 16:41:30 -0700131 private boolean mEnableHapticFeedback = true;
Andrei Onea15884392019-03-22 17:28:11 +0000132 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800133 private boolean mPatternInProgress = false;
Vishwath Mohan8c8b9fb2018-02-16 12:13:48 -0800134 private boolean mFadePattern = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800135
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800136 private float mHitFactor = 0.6f;
137
Andrei Onea15884392019-03-22 17:28:11 +0000138 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800139 private float mSquareWidth;
Andrei Onea15884392019-03-22 17:28:11 +0000140 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800141 private float mSquareHeight;
142
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800143 private final Path mCurrentPath = new Path();
144 private final Rect mInvalidate = new Rect();
Jim Miller9ddfeb82013-04-10 18:15:30 -0700145 private final Rect mTmpInvalidateRect = new Rect();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800146
Jim Millerbf1259b2010-03-31 18:15:27 -0700147 private int mAspect;
Jorim Jaggic1581972014-08-07 22:15:48 +0200148 private int mRegularColor;
149 private int mErrorColor;
150 private int mSuccessColor;
Selim Cinek2cb687e2014-06-04 16:06:51 +0200151
Paul Crowleya97227a2014-10-23 10:25:41 +0100152 private final Interpolator mFastOutSlowInInterpolator;
153 private final Interpolator mLinearOutSlowInInterpolator;
Jim Miller240a2952015-06-10 16:55:24 -0700154 private PatternExploreByTouchHelper mExploreByTouchHelper;
155 private AudioManager mAudioManager;
Jim Millerbf1259b2010-03-31 18:15:27 -0700156
Alain Vongsouvanh88a2a612016-11-18 13:27:16 -0800157 private Drawable mSelectedDrawable;
158 private Drawable mNotSelectedDrawable;
159 private boolean mUseLockPatternDrawable;
160
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800161 /**
162 * Represents a cell in the 3 X 3 matrix of the unlock pattern view.
163 */
Paul Crowleya97227a2014-10-23 10:25:41 +0100164 public static final class Cell {
Andrei Onea15884392019-03-22 17:28:11 +0000165 @UnsupportedAppUsage
Paul Crowleya97227a2014-10-23 10:25:41 +0100166 final int row;
Andrei Onea15884392019-03-22 17:28:11 +0000167 @UnsupportedAppUsage
Paul Crowleya97227a2014-10-23 10:25:41 +0100168 final int column;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800169
170 // keep # objects limited to 9
Paul Crowleya97227a2014-10-23 10:25:41 +0100171 private static final Cell[][] sCells = createCells();
172
173 private static Cell[][] createCells() {
174 Cell[][] res = new Cell[3][3];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800175 for (int i = 0; i < 3; i++) {
176 for (int j = 0; j < 3; j++) {
Paul Crowleya97227a2014-10-23 10:25:41 +0100177 res[i][j] = new Cell(i, j);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800178 }
179 }
Paul Crowleya97227a2014-10-23 10:25:41 +0100180 return res;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800181 }
182
183 /**
184 * @param row The row of the cell.
185 * @param column The column of the cell.
186 */
187 private Cell(int row, int column) {
188 checkRange(row, column);
189 this.row = row;
190 this.column = column;
191 }
192
193 public int getRow() {
194 return row;
195 }
196
197 public int getColumn() {
198 return column;
199 }
200
Paul Crowleya97227a2014-10-23 10:25:41 +0100201 public static Cell of(int row, int column) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800202 checkRange(row, column);
203 return sCells[row][column];
204 }
205
206 private static void checkRange(int row, int column) {
207 if (row < 0 || row > 2) {
208 throw new IllegalArgumentException("row must be in range 0-2");
209 }
210 if (column < 0 || column > 2) {
211 throw new IllegalArgumentException("column must be in range 0-2");
212 }
213 }
214
Paul Crowleya97227a2014-10-23 10:25:41 +0100215 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800216 public String toString() {
217 return "(row=" + row + ",clmn=" + column + ")";
218 }
219 }
220
Selim Cinek30181972014-05-30 03:33:06 +0200221 public static class CellState {
Jorim Jaggi613f55f2015-07-16 15:30:10 -0700222 int row;
223 int col;
224 boolean hwAnimating;
225 CanvasProperty<Float> hwRadius;
226 CanvasProperty<Float> hwCenterX;
227 CanvasProperty<Float> hwCenterY;
228 CanvasProperty<Paint> hwPaint;
229 float radius;
230 float translationY;
231 float alpha = 1f;
Jorim Jaggic1581972014-08-07 22:15:48 +0200232 public float lineEndX = Float.MIN_VALUE;
233 public float lineEndY = Float.MIN_VALUE;
234 public ValueAnimator lineAnimator;
Selim Cinek30181972014-05-30 03:33:06 +0200235 }
236
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800237 /**
238 * How to display the current pattern.
239 */
240 public enum DisplayMode {
241
242 /**
243 * The pattern drawn is correct (i.e draw it in a friendly color)
244 */
Andrei Onea15884392019-03-22 17:28:11 +0000245 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800246 Correct,
247
248 /**
249 * Animate the pattern (for demo, and help).
250 */
Andrei Onea15884392019-03-22 17:28:11 +0000251 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800252 Animate,
253
254 /**
255 * The pattern is wrong (i.e draw a foreboding color)
256 */
Andrei Onea15884392019-03-22 17:28:11 +0000257 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800258 Wrong
259 }
260
261 /**
262 * The call back interface for detecting patterns entered by the user.
263 */
264 public static interface OnPatternListener {
265
266 /**
267 * A new pattern has begun.
268 */
269 void onPatternStart();
270
271 /**
272 * The pattern was cleared.
273 */
274 void onPatternCleared();
275
276 /**
Jim Miller41e8dc02009-09-29 14:16:21 -0700277 * The user extended the pattern currently being drawn by one cell.
278 * @param pattern The pattern with newly added cell.
279 */
280 void onPatternCellAdded(List<Cell> pattern);
281
282 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800283 * A pattern was detected from the user.
284 * @param pattern The pattern.
285 */
286 void onPatternDetected(List<Cell> pattern);
287 }
288
289 public LockPatternView(Context context) {
290 this(context, null);
291 }
292
Andrei Onea15884392019-03-22 17:28:11 +0000293 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800294 public LockPatternView(Context context, AttributeSet attrs) {
295 super(context, attrs);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800296
Jason Monk58be7a62017-02-01 20:17:51 -0500297 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LockPatternView,
298 R.attr.lockPatternStyle, R.style.Widget_LockPatternView);
Jim Millerbf1259b2010-03-31 18:15:27 -0700299
300 final String aspect = a.getString(R.styleable.LockPatternView_aspect);
301
302 if ("square".equals(aspect)) {
303 mAspect = ASPECT_SQUARE;
304 } else if ("lock_width".equals(aspect)) {
305 mAspect = ASPECT_LOCK_WIDTH;
306 } else if ("lock_height".equals(aspect)) {
307 mAspect = ASPECT_LOCK_HEIGHT;
308 } else {
309 mAspect = ASPECT_SQUARE;
310 }
311
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800312 setClickable(true);
313
Selim Cinek2cb687e2014-06-04 16:06:51 +0200314
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800315 mPathPaint.setAntiAlias(true);
316 mPathPaint.setDither(true);
Fabrice Di Meglio1cf35942014-03-31 16:50:29 -0700317
Jason Monk58be7a62017-02-01 20:17:51 -0500318 mRegularColor = a.getColor(R.styleable.LockPatternView_regularColor, 0);
319 mErrorColor = a.getColor(R.styleable.LockPatternView_errorColor, 0);
320 mSuccessColor = a.getColor(R.styleable.LockPatternView_successColor, 0);
Fabrice Di Meglio1cf35942014-03-31 16:50:29 -0700321
Jorim Jaggic1581972014-08-07 22:15:48 +0200322 int pathColor = a.getColor(R.styleable.LockPatternView_pathColor, mRegularColor);
Selim Cinek2cb687e2014-06-04 16:06:51 +0200323 mPathPaint.setColor(pathColor);
Fabrice Di Meglio1cf35942014-03-31 16:50:29 -0700324
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800325 mPathPaint.setStyle(Paint.Style.STROKE);
326 mPathPaint.setStrokeJoin(Paint.Join.ROUND);
327 mPathPaint.setStrokeCap(Paint.Cap.ROUND);
328
Jorim Jaggic1581972014-08-07 22:15:48 +0200329 mPathWidth = getResources().getDimensionPixelSize(R.dimen.lock_pattern_dot_line_width);
330 mPathPaint.setStrokeWidth(mPathWidth);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800331
Jorim Jaggic1581972014-08-07 22:15:48 +0200332 mDotSize = getResources().getDimensionPixelSize(R.dimen.lock_pattern_dot_size);
333 mDotSizeActivated = getResources().getDimensionPixelSize(
334 R.dimen.lock_pattern_dot_size_activated);
Jim Miller85d63002009-09-30 02:50:02 -0700335
Alain Vongsouvanh88a2a612016-11-18 13:27:16 -0800336 mUseLockPatternDrawable = getResources().getBoolean(R.bool.use_lock_pattern_drawable);
337 if (mUseLockPatternDrawable) {
338 mSelectedDrawable = getResources().getDrawable(R.drawable.lockscreen_selected);
339 mNotSelectedDrawable = getResources().getDrawable(R.drawable.lockscreen_notselected);
340 }
341
Selim Cinek2cb687e2014-06-04 16:06:51 +0200342 mPaint.setAntiAlias(true);
343 mPaint.setDither(true);
Selim Cinek30181972014-05-30 03:33:06 +0200344
345 mCellStates = new CellState[3][3];
346 for (int i = 0; i < 3; i++) {
347 for (int j = 0; j < 3; j++) {
348 mCellStates[i][j] = new CellState();
Jorim Jaggi613f55f2015-07-16 15:30:10 -0700349 mCellStates[i][j].radius = mDotSize/2;
350 mCellStates[i][j].row = i;
351 mCellStates[i][j].col = j;
Selim Cinek30181972014-05-30 03:33:06 +0200352 }
353 }
Jorim Jaggic1581972014-08-07 22:15:48 +0200354
355 mFastOutSlowInInterpolator =
356 AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in);
357 mLinearOutSlowInInterpolator =
358 AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in);
Jim Miller240a2952015-06-10 16:55:24 -0700359 mExploreByTouchHelper = new PatternExploreByTouchHelper(this);
360 setAccessibilityDelegate(mExploreByTouchHelper);
361 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
Jim Millerac010472016-02-02 18:12:30 -0800362 a.recycle();
Selim Cinek30181972014-05-30 03:33:06 +0200363 }
364
Andrei Onea15884392019-03-22 17:28:11 +0000365 @UnsupportedAppUsage
Selim Cinek30181972014-05-30 03:33:06 +0200366 public CellState[][] getCellStates() {
367 return mCellStates;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800368 }
369
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800370 /**
371 * @return Whether the view is in stealth mode.
372 */
373 public boolean isInStealthMode() {
374 return mInStealthMode;
375 }
376
377 /**
378 * @return Whether the view has tactile feedback enabled.
379 */
380 public boolean isTactileFeedbackEnabled() {
Jim Milleraef555b2011-10-12 16:41:30 -0700381 return mEnableHapticFeedback;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800382 }
383
384 /**
385 * Set whether the view is in stealth mode. If true, there will be no
386 * visible feedback as the user enters the pattern.
387 *
388 * @param inStealthMode Whether in stealth mode.
389 */
Andrei Onea15884392019-03-22 17:28:11 +0000390 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800391 public void setInStealthMode(boolean inStealthMode) {
392 mInStealthMode = inStealthMode;
393 }
394
395 /**
Vishwath Mohan8c8b9fb2018-02-16 12:13:48 -0800396 * Set whether the pattern should fade as it's being drawn. If
397 * true, each segment of the pattern fades over time.
398 */
399 public void setFadePattern(boolean fadePattern) {
400 mFadePattern = fadePattern;
401 }
402
403 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800404 * Set whether the view will use tactile feedback. If true, there will be
405 * tactile feedback as the user enters the pattern.
406 *
407 * @param tactileFeedbackEnabled Whether tactile feedback is enabled
408 */
Andrei Onea15884392019-03-22 17:28:11 +0000409 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800410 public void setTactileFeedbackEnabled(boolean tactileFeedbackEnabled) {
Jim Milleraef555b2011-10-12 16:41:30 -0700411 mEnableHapticFeedback = tactileFeedbackEnabled;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800412 }
413
414 /**
415 * Set the call back for pattern detection.
416 * @param onPatternListener The call back.
417 */
Andrei Onea15884392019-03-22 17:28:11 +0000418 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800419 public void setOnPatternListener(
420 OnPatternListener onPatternListener) {
421 mOnPatternListener = onPatternListener;
422 }
423
424 /**
425 * Set the pattern explicitely (rather than waiting for the user to input
426 * a pattern).
427 * @param displayMode How to display the pattern.
428 * @param pattern The pattern.
429 */
430 public void setPattern(DisplayMode displayMode, List<Cell> pattern) {
431 mPattern.clear();
432 mPattern.addAll(pattern);
433 clearPatternDrawLookup();
434 for (Cell cell : pattern) {
435 mPatternDrawLookup[cell.getRow()][cell.getColumn()] = true;
436 }
437
438 setDisplayMode(displayMode);
439 }
440
441 /**
442 * Set the display mode of the current pattern. This can be useful, for
443 * instance, after detecting a pattern to tell this view whether change the
444 * in progress result to correct or wrong.
445 * @param displayMode The display mode.
446 */
Andrei Onea15884392019-03-22 17:28:11 +0000447 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800448 public void setDisplayMode(DisplayMode displayMode) {
449 mPatternDisplayMode = displayMode;
450 if (displayMode == DisplayMode.Animate) {
451 if (mPattern.size() == 0) {
452 throw new IllegalStateException("you must have a pattern to "
453 + "animate if you want to set the display mode to animate");
454 }
455 mAnimatingPeriodStart = SystemClock.elapsedRealtime();
456 final Cell first = mPattern.get(0);
457 mInProgressX = getCenterXForColumn(first.getColumn());
458 mInProgressY = getCenterYForRow(first.getRow());
459 clearPatternDrawLookup();
460 }
461 invalidate();
462 }
463
Jorim Jaggi613f55f2015-07-16 15:30:10 -0700464 public void startCellStateAnimation(CellState cellState, float startAlpha, float endAlpha,
465 float startTranslationY, float endTranslationY, float startScale, float endScale,
466 long delay, long duration,
467 Interpolator interpolator, Runnable finishRunnable) {
468 if (isHardwareAccelerated()) {
469 startCellStateAnimationHw(cellState, startAlpha, endAlpha, startTranslationY,
470 endTranslationY, startScale, endScale, delay, duration, interpolator,
471 finishRunnable);
472 } else {
473 startCellStateAnimationSw(cellState, startAlpha, endAlpha, startTranslationY,
474 endTranslationY, startScale, endScale, delay, duration, interpolator,
475 finishRunnable);
476 }
477 }
478
479 private void startCellStateAnimationSw(final CellState cellState,
480 final float startAlpha, final float endAlpha,
481 final float startTranslationY, final float endTranslationY,
482 final float startScale, final float endScale,
483 long delay, long duration, Interpolator interpolator, final Runnable finishRunnable) {
484 cellState.alpha = startAlpha;
485 cellState.translationY = startTranslationY;
486 cellState.radius = mDotSize/2 * startScale;
487 ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
488 animator.setDuration(duration);
489 animator.setStartDelay(delay);
490 animator.setInterpolator(interpolator);
491 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
492 @Override
493 public void onAnimationUpdate(ValueAnimator animation) {
494 float t = (float) animation.getAnimatedValue();
495 cellState.alpha = (1 - t) * startAlpha + t * endAlpha;
496 cellState.translationY = (1 - t) * startTranslationY + t * endTranslationY;
497 cellState.radius = mDotSize/2 * ((1 - t) * startScale + t * endScale);
498 invalidate();
499 }
500 });
501 animator.addListener(new AnimatorListenerAdapter() {
502 @Override
503 public void onAnimationEnd(Animator animation) {
504 if (finishRunnable != null) {
505 finishRunnable.run();
506 }
507 }
508 });
509 animator.start();
510 }
511
512 private void startCellStateAnimationHw(final CellState cellState,
513 float startAlpha, float endAlpha,
514 float startTranslationY, float endTranslationY,
515 float startScale, float endScale,
516 long delay, long duration, Interpolator interpolator, final Runnable finishRunnable) {
517 cellState.alpha = endAlpha;
518 cellState.translationY = endTranslationY;
519 cellState.radius = mDotSize/2 * endScale;
520 cellState.hwAnimating = true;
521 cellState.hwCenterY = CanvasProperty.createFloat(
522 getCenterYForRow(cellState.row) + startTranslationY);
523 cellState.hwCenterX = CanvasProperty.createFloat(getCenterXForColumn(cellState.col));
524 cellState.hwRadius = CanvasProperty.createFloat(mDotSize/2 * startScale);
525 mPaint.setColor(getCurrentColor(false));
526 mPaint.setAlpha((int) (startAlpha * 255));
527 cellState.hwPaint = CanvasProperty.createPaint(new Paint(mPaint));
528
529 startRtFloatAnimation(cellState.hwCenterY,
530 getCenterYForRow(cellState.row) + endTranslationY, delay, duration, interpolator);
531 startRtFloatAnimation(cellState.hwRadius, mDotSize/2 * endScale, delay, duration,
532 interpolator);
533 startRtAlphaAnimation(cellState, endAlpha, delay, duration, interpolator,
534 new AnimatorListenerAdapter() {
535 @Override
536 public void onAnimationEnd(Animator animation) {
537 cellState.hwAnimating = false;
538 if (finishRunnable != null) {
539 finishRunnable.run();
540 }
541 }
542 });
543
544 invalidate();
545 }
546
547 private void startRtAlphaAnimation(CellState cellState, float endAlpha,
548 long delay, long duration, Interpolator interpolator,
549 Animator.AnimatorListener listener) {
550 RenderNodeAnimator animator = new RenderNodeAnimator(cellState.hwPaint,
551 RenderNodeAnimator.PAINT_ALPHA, (int) (endAlpha * 255));
552 animator.setDuration(duration);
553 animator.setStartDelay(delay);
554 animator.setInterpolator(interpolator);
555 animator.setTarget(this);
556 animator.addListener(listener);
557 animator.start();
558 }
559
560 private void startRtFloatAnimation(CanvasProperty<Float> property, float endValue,
561 long delay, long duration, Interpolator interpolator) {
562 RenderNodeAnimator animator = new RenderNodeAnimator(property, endValue);
563 animator.setDuration(duration);
564 animator.setStartDelay(delay);
565 animator.setInterpolator(interpolator);
566 animator.setTarget(this);
567 animator.start();
568 }
569
Svetoslav Ganov530d9f12011-10-04 14:51:25 -0700570 private void notifyCellAdded() {
Jim Miller240a2952015-06-10 16:55:24 -0700571 // sendAccessEvent(R.string.lockscreen_access_pattern_cell_added);
Svetoslav Ganov530d9f12011-10-04 14:51:25 -0700572 if (mOnPatternListener != null) {
573 mOnPatternListener.onPatternCellAdded(mPattern);
574 }
Jim Miller240a2952015-06-10 16:55:24 -0700575 // Disable used cells for accessibility as they get added
576 if (DEBUG_A11Y) Log.v(TAG, "ivnalidating root because cell was added.");
577 mExploreByTouchHelper.invalidateRoot();
Svetoslav Ganov530d9f12011-10-04 14:51:25 -0700578 }
579
580 private void notifyPatternStarted() {
alanve303c5c2012-10-03 13:15:14 -0700581 sendAccessEvent(R.string.lockscreen_access_pattern_start);
Svetoslav Ganov530d9f12011-10-04 14:51:25 -0700582 if (mOnPatternListener != null) {
583 mOnPatternListener.onPatternStart();
584 }
Svetoslav Ganov530d9f12011-10-04 14:51:25 -0700585 }
586
Andrei Onea15884392019-03-22 17:28:11 +0000587 @UnsupportedAppUsage
Svetoslav Ganov530d9f12011-10-04 14:51:25 -0700588 private void notifyPatternDetected() {
alanve303c5c2012-10-03 13:15:14 -0700589 sendAccessEvent(R.string.lockscreen_access_pattern_detected);
Svetoslav Ganov530d9f12011-10-04 14:51:25 -0700590 if (mOnPatternListener != null) {
591 mOnPatternListener.onPatternDetected(mPattern);
592 }
Svetoslav Ganov530d9f12011-10-04 14:51:25 -0700593 }
594
595 private void notifyPatternCleared() {
alanve303c5c2012-10-03 13:15:14 -0700596 sendAccessEvent(R.string.lockscreen_access_pattern_cleared);
Svetoslav Ganov530d9f12011-10-04 14:51:25 -0700597 if (mOnPatternListener != null) {
598 mOnPatternListener.onPatternCleared();
599 }
Svetoslav Ganov530d9f12011-10-04 14:51:25 -0700600 }
601
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800602 /**
603 * Clear the pattern.
604 */
Andrei Onea15884392019-03-22 17:28:11 +0000605 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800606 public void clearPattern() {
607 resetPattern();
608 }
609
Jim Miller240a2952015-06-10 16:55:24 -0700610 @Override
611 protected boolean dispatchHoverEvent(MotionEvent event) {
Adrian Roosd2def942015-07-27 13:49:53 -0700612 // Dispatch to onHoverEvent first so mPatternInProgress is up to date when the
613 // helper gets the event.
614 boolean handled = super.dispatchHoverEvent(event);
615 handled |= mExploreByTouchHelper.dispatchHoverEvent(event);
616 return handled;
Jim Miller240a2952015-06-10 16:55:24 -0700617 }
618
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800619 /**
620 * Reset all pattern state.
621 */
622 private void resetPattern() {
623 mPattern.clear();
624 clearPatternDrawLookup();
625 mPatternDisplayMode = DisplayMode.Correct;
626 invalidate();
627 }
628
629 /**
Vishwath Mohandcc1f1942018-01-24 16:54:25 -0800630 * Clear the pattern lookup table. Also reset the line fade start times for
631 * the next attempt.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800632 */
633 private void clearPatternDrawLookup() {
634 for (int i = 0; i < 3; i++) {
635 for (int j = 0; j < 3; j++) {
636 mPatternDrawLookup[i][j] = false;
Lucas Dupin2d1ef0b2018-06-04 17:51:37 -0700637 mLineFadeStart[i+j*3] = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800638 }
639 }
640 }
641
642 /**
643 * Disable input (for instance when displaying a message that will
644 * timeout so user doesn't get view into messy state).
645 */
Andrei Onea15884392019-03-22 17:28:11 +0000646 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800647 public void disableInput() {
648 mInputEnabled = false;
649 }
650
651 /**
652 * Enable input.
653 */
Andrei Onea15884392019-03-22 17:28:11 +0000654 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800655 public void enableInput() {
656 mInputEnabled = true;
657 }
658
659 @Override
660 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
661 final int width = w - mPaddingLeft - mPaddingRight;
662 mSquareWidth = width / 3.0f;
663
Jim Miller240a2952015-06-10 16:55:24 -0700664 if (DEBUG_A11Y) Log.v(TAG, "onSizeChanged(" + w + "," + h + ")");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800665 final int height = h - mPaddingTop - mPaddingBottom;
666 mSquareHeight = height / 3.0f;
Jim Miller240a2952015-06-10 16:55:24 -0700667 mExploreByTouchHelper.invalidateRoot();
Alain Vongsouvanh88a2a612016-11-18 13:27:16 -0800668
669 if (mUseLockPatternDrawable) {
670 mNotSelectedDrawable.setBounds(mPaddingLeft, mPaddingTop, width, height);
671 mSelectedDrawable.setBounds(mPaddingLeft, mPaddingTop, width, height);
672 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800673 }
674
Jim Miller0a075382010-12-15 09:28:56 -0800675 private int resolveMeasured(int measureSpec, int desired)
676 {
677 int result = 0;
678 int specSize = MeasureSpec.getSize(measureSpec);
679 switch (MeasureSpec.getMode(measureSpec)) {
680 case MeasureSpec.UNSPECIFIED:
681 result = desired;
682 break;
683 case MeasureSpec.AT_MOST:
Peter Ng7bc60ab2011-11-09 15:13:51 -0800684 result = Math.max(specSize, desired);
Jim Miller0a075382010-12-15 09:28:56 -0800685 break;
686 case MeasureSpec.EXACTLY:
687 default:
Philip Milne1fd16372011-06-21 14:57:47 -0700688 result = specSize;
Jim Miller0a075382010-12-15 09:28:56 -0800689 }
690 return result;
691 }
692
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800693 @Override
694 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Jim Miller0d244192011-06-16 17:31:21 -0700695 final int minimumWidth = getSuggestedMinimumWidth();
696 final int minimumHeight = getSuggestedMinimumHeight();
Jim Miller0a075382010-12-15 09:28:56 -0800697 int viewWidth = resolveMeasured(widthMeasureSpec, minimumWidth);
698 int viewHeight = resolveMeasured(heightMeasureSpec, minimumHeight);
699
Jim Millerbf1259b2010-03-31 18:15:27 -0700700 switch (mAspect) {
701 case ASPECT_SQUARE:
Jim Miller0d244192011-06-16 17:31:21 -0700702 viewWidth = viewHeight = Math.min(viewWidth, viewHeight);
Jim Millerbf1259b2010-03-31 18:15:27 -0700703 break;
704 case ASPECT_LOCK_WIDTH:
Jim Miller0d244192011-06-16 17:31:21 -0700705 viewHeight = Math.min(viewWidth, viewHeight);
Jim Millerbf1259b2010-03-31 18:15:27 -0700706 break;
707 case ASPECT_LOCK_HEIGHT:
Jim Miller0d244192011-06-16 17:31:21 -0700708 viewWidth = Math.min(viewWidth, viewHeight);
Jim Millerbf1259b2010-03-31 18:15:27 -0700709 break;
710 }
Jim Miller0a075382010-12-15 09:28:56 -0800711 // Log.v(TAG, "LockPatternView dimensions: " + viewWidth + "x" + viewHeight);
Jim Millerbf1259b2010-03-31 18:15:27 -0700712 setMeasuredDimension(viewWidth, viewHeight);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800713 }
714
715 /**
716 * Determines whether the point x, y will add a new point to the current
717 * pattern (in addition to finding the cell, also makes heuristic choices
718 * such as filling in gaps based on current pattern).
719 * @param x The x coordinate.
720 * @param y The y coordinate.
721 */
722 private Cell detectAndAddHit(float x, float y) {
723 final Cell cell = checkForNewHit(x, y);
724 if (cell != null) {
725
726 // check for gaps in existing pattern
727 Cell fillInGapCell = null;
728 final ArrayList<Cell> pattern = mPattern;
729 if (!pattern.isEmpty()) {
730 final Cell lastCell = pattern.get(pattern.size() - 1);
731 int dRow = cell.row - lastCell.row;
732 int dColumn = cell.column - lastCell.column;
733
734 int fillInRow = lastCell.row;
735 int fillInColumn = lastCell.column;
736
737 if (Math.abs(dRow) == 2 && Math.abs(dColumn) != 1) {
738 fillInRow = lastCell.row + ((dRow > 0) ? 1 : -1);
739 }
740
741 if (Math.abs(dColumn) == 2 && Math.abs(dRow) != 1) {
742 fillInColumn = lastCell.column + ((dColumn > 0) ? 1 : -1);
743 }
744
745 fillInGapCell = Cell.of(fillInRow, fillInColumn);
746 }
747
748 if (fillInGapCell != null &&
749 !mPatternDrawLookup[fillInGapCell.row][fillInGapCell.column]) {
750 addCellToPattern(fillInGapCell);
751 }
752 addCellToPattern(cell);
Jim Milleraef555b2011-10-12 16:41:30 -0700753 if (mEnableHapticFeedback) {
754 performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
755 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING
756 | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800757 }
758 return cell;
759 }
760 return null;
761 }
762
763 private void addCellToPattern(Cell newCell) {
764 mPatternDrawLookup[newCell.getRow()][newCell.getColumn()] = true;
765 mPattern.add(newCell);
Jorim Jaggic1581972014-08-07 22:15:48 +0200766 if (!mInStealthMode) {
767 startCellActivatedAnimation(newCell);
768 }
Svetoslav Ganov530d9f12011-10-04 14:51:25 -0700769 notifyCellAdded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800770 }
771
Jorim Jaggic1581972014-08-07 22:15:48 +0200772 private void startCellActivatedAnimation(Cell cell) {
773 final CellState cellState = mCellStates[cell.row][cell.column];
Jorim Jaggi613f55f2015-07-16 15:30:10 -0700774 startRadiusAnimation(mDotSize/2, mDotSizeActivated/2, 96, mLinearOutSlowInInterpolator,
Jorim Jaggic1581972014-08-07 22:15:48 +0200775 cellState, new Runnable() {
Jorim Jaggi613f55f2015-07-16 15:30:10 -0700776 @Override
777 public void run() {
778 startRadiusAnimation(mDotSizeActivated/2, mDotSize/2, 192,
779 mFastOutSlowInInterpolator,
780 cellState, null);
781 }
782 });
Jorim Jaggic1581972014-08-07 22:15:48 +0200783 startLineEndAnimation(cellState, mInProgressX, mInProgressY,
784 getCenterXForColumn(cell.column), getCenterYForRow(cell.row));
785 }
786
787 private void startLineEndAnimation(final CellState state,
788 final float startX, final float startY, final float targetX, final float targetY) {
789 ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
790 valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
791 @Override
792 public void onAnimationUpdate(ValueAnimator animation) {
793 float t = (float) animation.getAnimatedValue();
794 state.lineEndX = (1 - t) * startX + t * targetX;
795 state.lineEndY = (1 - t) * startY + t * targetY;
796 invalidate();
797 }
798 });
799 valueAnimator.addListener(new AnimatorListenerAdapter() {
800 @Override
801 public void onAnimationEnd(Animator animation) {
802 state.lineAnimator = null;
803 }
804 });
805 valueAnimator.setInterpolator(mFastOutSlowInInterpolator);
806 valueAnimator.setDuration(100);
807 valueAnimator.start();
808 state.lineAnimator = valueAnimator;
809 }
810
Jorim Jaggi613f55f2015-07-16 15:30:10 -0700811 private void startRadiusAnimation(float start, float end, long duration,
812 Interpolator interpolator, final CellState state, final Runnable endRunnable) {
Jorim Jaggic1581972014-08-07 22:15:48 +0200813 ValueAnimator valueAnimator = ValueAnimator.ofFloat(start, end);
814 valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
815 @Override
816 public void onAnimationUpdate(ValueAnimator animation) {
Jorim Jaggi613f55f2015-07-16 15:30:10 -0700817 state.radius = (float) animation.getAnimatedValue();
Jorim Jaggic1581972014-08-07 22:15:48 +0200818 invalidate();
819 }
820 });
821 if (endRunnable != null) {
822 valueAnimator.addListener(new AnimatorListenerAdapter() {
823 @Override
824 public void onAnimationEnd(Animator animation) {
825 endRunnable.run();
826 }
827 });
828 }
829 valueAnimator.setInterpolator(interpolator);
830 valueAnimator.setDuration(duration);
831 valueAnimator.start();
832 }
833
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800834 // helper method to find which cell a point maps to
835 private Cell checkForNewHit(float x, float y) {
836
837 final int rowHit = getRowHit(y);
838 if (rowHit < 0) {
839 return null;
840 }
841 final int columnHit = getColumnHit(x);
842 if (columnHit < 0) {
843 return null;
844 }
845
846 if (mPatternDrawLookup[rowHit][columnHit]) {
847 return null;
848 }
849 return Cell.of(rowHit, columnHit);
850 }
851
852 /**
853 * Helper method to find the row that y falls into.
854 * @param y The y coordinate
855 * @return The row that y falls in, or -1 if it falls in no row.
856 */
857 private int getRowHit(float y) {
858
859 final float squareHeight = mSquareHeight;
860 float hitSize = squareHeight * mHitFactor;
861
862 float offset = mPaddingTop + (squareHeight - hitSize) / 2f;
863 for (int i = 0; i < 3; i++) {
864
865 final float hitTop = offset + squareHeight * i;
866 if (y >= hitTop && y <= hitTop + hitSize) {
867 return i;
868 }
869 }
870 return -1;
871 }
872
873 /**
874 * Helper method to find the column x fallis into.
875 * @param x The x coordinate.
876 * @return The column that x falls in, or -1 if it falls in no column.
877 */
878 private int getColumnHit(float x) {
879 final float squareWidth = mSquareWidth;
880 float hitSize = squareWidth * mHitFactor;
881
882 float offset = mPaddingLeft + (squareWidth - hitSize) / 2f;
883 for (int i = 0; i < 3; i++) {
884
885 final float hitLeft = offset + squareWidth * i;
886 if (x >= hitLeft && x <= hitLeft + hitSize) {
887 return i;
888 }
889 }
890 return -1;
891 }
892
893 @Override
Svetoslav Ganov530d9f12011-10-04 14:51:25 -0700894 public boolean onHoverEvent(MotionEvent event) {
895 if (AccessibilityManager.getInstance(mContext).isTouchExplorationEnabled()) {
896 final int action = event.getAction();
897 switch (action) {
898 case MotionEvent.ACTION_HOVER_ENTER:
899 event.setAction(MotionEvent.ACTION_DOWN);
900 break;
901 case MotionEvent.ACTION_HOVER_MOVE:
902 event.setAction(MotionEvent.ACTION_MOVE);
903 break;
904 case MotionEvent.ACTION_HOVER_EXIT:
905 event.setAction(MotionEvent.ACTION_UP);
906 break;
907 }
908 onTouchEvent(event);
909 event.setAction(action);
910 }
911 return super.onHoverEvent(event);
912 }
913
914 @Override
Jim Milleraced12f2011-06-17 16:09:46 -0700915 public boolean onTouchEvent(MotionEvent event) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800916 if (!mInputEnabled || !isEnabled()) {
917 return false;
918 }
919
Jim Milleraced12f2011-06-17 16:09:46 -0700920 switch(event.getAction()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800921 case MotionEvent.ACTION_DOWN:
Jim Milleraced12f2011-06-17 16:09:46 -0700922 handleActionDown(event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800923 return true;
924 case MotionEvent.ACTION_UP:
Paul Crowleya97227a2014-10-23 10:25:41 +0100925 handleActionUp();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800926 return true;
927 case MotionEvent.ACTION_MOVE:
Jim Milleraced12f2011-06-17 16:09:46 -0700928 handleActionMove(event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800929 return true;
930 case MotionEvent.ACTION_CANCEL:
Svetoslav Ganovc4842c12012-10-31 14:33:32 -0700931 if (mPatternInProgress) {
Adrian Roosd2def942015-07-27 13:49:53 -0700932 setPatternInProgress(false);
Svetoslav Ganovc4842c12012-10-31 14:33:32 -0700933 resetPattern();
934 notifyPatternCleared();
935 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800936 if (PROFILE_DRAWING) {
937 if (mDrawingProfilingStarted) {
938 Debug.stopMethodTracing();
939 mDrawingProfilingStarted = false;
940 }
941 }
942 return true;
943 }
944 return false;
945 }
946
Adrian Roosd2def942015-07-27 13:49:53 -0700947 private void setPatternInProgress(boolean progress) {
948 mPatternInProgress = progress;
949 mExploreByTouchHelper.invalidateRoot();
950 }
951
Jim Milleraced12f2011-06-17 16:09:46 -0700952 private void handleActionMove(MotionEvent event) {
953 // Handle all recent motion events so we don't skip any cells even when the device
954 // is busy...
Jorim Jaggic1581972014-08-07 22:15:48 +0200955 final float radius = mPathWidth;
Jim Milleraced12f2011-06-17 16:09:46 -0700956 final int historySize = event.getHistorySize();
Jim Miller9ddfeb82013-04-10 18:15:30 -0700957 mTmpInvalidateRect.setEmpty();
Jim Miller0caa3772013-03-07 18:44:32 -0800958 boolean invalidateNow = false;
Jim Milleraced12f2011-06-17 16:09:46 -0700959 for (int i = 0; i < historySize + 1; i++) {
960 final float x = i < historySize ? event.getHistoricalX(i) : event.getX();
961 final float y = i < historySize ? event.getHistoricalY(i) : event.getY();
Jim Milleraced12f2011-06-17 16:09:46 -0700962 Cell hitCell = detectAndAddHit(x, y);
963 final int patternSize = mPattern.size();
Svetoslav Ganov530d9f12011-10-04 14:51:25 -0700964 if (hitCell != null && patternSize == 1) {
Adrian Roosd2def942015-07-27 13:49:53 -0700965 setPatternInProgress(true);
Svetoslav Ganov530d9f12011-10-04 14:51:25 -0700966 notifyPatternStarted();
Jim Milleraced12f2011-06-17 16:09:46 -0700967 }
968 // note current x and y for rubber banding of in progress patterns
969 final float dx = Math.abs(x - mInProgressX);
970 final float dy = Math.abs(y - mInProgressY);
Jim Miller0caa3772013-03-07 18:44:32 -0800971 if (dx > DRAG_THRESHHOLD || dy > DRAG_THRESHHOLD) {
972 invalidateNow = true;
Jim Milleraced12f2011-06-17 16:09:46 -0700973 }
Jim Miller0caa3772013-03-07 18:44:32 -0800974
975 if (mPatternInProgress && patternSize > 0) {
976 final ArrayList<Cell> pattern = mPattern;
977 final Cell lastCell = pattern.get(patternSize - 1);
Jim Miller9ddfeb82013-04-10 18:15:30 -0700978 float lastCellCenterX = getCenterXForColumn(lastCell.column);
979 float lastCellCenterY = getCenterYForRow(lastCell.row);
Jim Miller0caa3772013-03-07 18:44:32 -0800980
Jim Miller9ddfeb82013-04-10 18:15:30 -0700981 // Adjust for drawn segment from last cell to (x,y). Radius accounts for line width.
982 float left = Math.min(lastCellCenterX, x) - radius;
983 float right = Math.max(lastCellCenterX, x) + radius;
984 float top = Math.min(lastCellCenterY, y) - radius;
985 float bottom = Math.max(lastCellCenterY, y) + radius;
Jim Miller0caa3772013-03-07 18:44:32 -0800986
987 // Invalidate between the pattern's new cell and the pattern's previous cell
Jim Miller9ddfeb82013-04-10 18:15:30 -0700988 if (hitCell != null) {
Jim Miller0caa3772013-03-07 18:44:32 -0800989 final float width = mSquareWidth * 0.5f;
990 final float height = mSquareHeight * 0.5f;
Jim Miller9ddfeb82013-04-10 18:15:30 -0700991 final float hitCellCenterX = getCenterXForColumn(hitCell.column);
992 final float hitCellCenterY = getCenterYForRow(hitCell.row);
993
994 left = Math.min(hitCellCenterX - width, left);
995 right = Math.max(hitCellCenterX + width, right);
996 top = Math.min(hitCellCenterY - height, top);
997 bottom = Math.max(hitCellCenterY + height, bottom);
Jim Miller0caa3772013-03-07 18:44:32 -0800998 }
999
1000 // Invalidate between the pattern's last cell and the previous location
Jim Miller9ddfeb82013-04-10 18:15:30 -07001001 mTmpInvalidateRect.union(Math.round(left), Math.round(top),
Jim Miller0caa3772013-03-07 18:44:32 -08001002 Math.round(right), Math.round(bottom));
1003 }
1004 }
1005 mInProgressX = event.getX();
1006 mInProgressY = event.getY();
1007
1008 // To save updates, we only invalidate if the user moved beyond a certain amount.
1009 if (invalidateNow) {
Jim Miller9ddfeb82013-04-10 18:15:30 -07001010 mInvalidate.union(mTmpInvalidateRect);
1011 invalidate(mInvalidate);
1012 mInvalidate.set(mTmpInvalidateRect);
Jim Milleraced12f2011-06-17 16:09:46 -07001013 }
1014 }
1015
Svetoslav Ganov530d9f12011-10-04 14:51:25 -07001016 private void sendAccessEvent(int resId) {
alanve303c5c2012-10-03 13:15:14 -07001017 announceForAccessibility(mContext.getString(resId));
Svetoslav Ganov530d9f12011-10-04 14:51:25 -07001018 }
1019
Paul Crowleya97227a2014-10-23 10:25:41 +01001020 private void handleActionUp() {
Jim Milleraced12f2011-06-17 16:09:46 -07001021 // report pattern detected
Svetoslav Ganov530d9f12011-10-04 14:51:25 -07001022 if (!mPattern.isEmpty()) {
Adrian Roosd2def942015-07-27 13:49:53 -07001023 setPatternInProgress(false);
Jorim Jaggic1581972014-08-07 22:15:48 +02001024 cancelLineAnimations();
Svetoslav Ganov530d9f12011-10-04 14:51:25 -07001025 notifyPatternDetected();
Vishwath Mohan18dcd472018-03-29 17:56:33 +00001026 // Also clear pattern if fading is enabled
1027 if (mFadePattern) {
1028 clearPatternDrawLookup();
1029 mPatternDisplayMode = DisplayMode.Correct;
1030 }
Jim Milleraced12f2011-06-17 16:09:46 -07001031 invalidate();
1032 }
1033 if (PROFILE_DRAWING) {
1034 if (mDrawingProfilingStarted) {
1035 Debug.stopMethodTracing();
1036 mDrawingProfilingStarted = false;
1037 }
1038 }
1039 }
1040
Jorim Jaggic1581972014-08-07 22:15:48 +02001041 private void cancelLineAnimations() {
1042 for (int i = 0; i < 3; i++) {
1043 for (int j = 0; j < 3; j++) {
1044 CellState state = mCellStates[i][j];
1045 if (state.lineAnimator != null) {
1046 state.lineAnimator.cancel();
1047 state.lineEndX = Float.MIN_VALUE;
1048 state.lineEndY = Float.MIN_VALUE;
1049 }
1050 }
1051 }
1052 }
Jim Milleraced12f2011-06-17 16:09:46 -07001053 private void handleActionDown(MotionEvent event) {
1054 resetPattern();
1055 final float x = event.getX();
1056 final float y = event.getY();
1057 final Cell hitCell = detectAndAddHit(x, y);
Svetoslav Ganov530d9f12011-10-04 14:51:25 -07001058 if (hitCell != null) {
Adrian Roosd2def942015-07-27 13:49:53 -07001059 setPatternInProgress(true);
Jim Milleraced12f2011-06-17 16:09:46 -07001060 mPatternDisplayMode = DisplayMode.Correct;
Svetoslav Ganov530d9f12011-10-04 14:51:25 -07001061 notifyPatternStarted();
Svetoslav Ganovc4842c12012-10-31 14:33:32 -07001062 } else if (mPatternInProgress) {
Adrian Roosd2def942015-07-27 13:49:53 -07001063 setPatternInProgress(false);
Svetoslav Ganov530d9f12011-10-04 14:51:25 -07001064 notifyPatternCleared();
Jim Milleraced12f2011-06-17 16:09:46 -07001065 }
1066 if (hitCell != null) {
1067 final float startX = getCenterXForColumn(hitCell.column);
1068 final float startY = getCenterYForRow(hitCell.row);
1069
1070 final float widthOffset = mSquareWidth / 2f;
1071 final float heightOffset = mSquareHeight / 2f;
1072
1073 invalidate((int) (startX - widthOffset), (int) (startY - heightOffset),
1074 (int) (startX + widthOffset), (int) (startY + heightOffset));
1075 }
1076 mInProgressX = x;
1077 mInProgressY = y;
1078 if (PROFILE_DRAWING) {
1079 if (!mDrawingProfilingStarted) {
1080 Debug.startMethodTracing("LockPatternDrawing");
1081 mDrawingProfilingStarted = true;
1082 }
1083 }
1084 }
1085
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001086 private float getCenterXForColumn(int column) {
1087 return mPaddingLeft + column * mSquareWidth + mSquareWidth / 2f;
1088 }
1089
1090 private float getCenterYForRow(int row) {
1091 return mPaddingTop + row * mSquareHeight + mSquareHeight / 2f;
1092 }
1093
1094 @Override
1095 protected void onDraw(Canvas canvas) {
1096 final ArrayList<Cell> pattern = mPattern;
1097 final int count = pattern.size();
1098 final boolean[][] drawLookup = mPatternDrawLookup;
1099
1100 if (mPatternDisplayMode == DisplayMode.Animate) {
1101
1102 // figure out which circles to draw
1103
1104 // + 1 so we pause on complete pattern
1105 final int oneCycle = (count + 1) * MILLIS_PER_CIRCLE_ANIMATING;
1106 final int spotInCycle = (int) (SystemClock.elapsedRealtime() -
1107 mAnimatingPeriodStart) % oneCycle;
1108 final int numCircles = spotInCycle / MILLIS_PER_CIRCLE_ANIMATING;
1109
1110 clearPatternDrawLookup();
1111 for (int i = 0; i < numCircles; i++) {
1112 final Cell cell = pattern.get(i);
1113 drawLookup[cell.getRow()][cell.getColumn()] = true;
1114 }
1115
1116 // figure out in progress portion of ghosting line
1117
1118 final boolean needToUpdateInProgressPoint = numCircles > 0
1119 && numCircles < count;
1120
1121 if (needToUpdateInProgressPoint) {
1122 final float percentageOfNextCircle =
1123 ((float) (spotInCycle % MILLIS_PER_CIRCLE_ANIMATING)) /
1124 MILLIS_PER_CIRCLE_ANIMATING;
1125
1126 final Cell currentCell = pattern.get(numCircles - 1);
1127 final float centerX = getCenterXForColumn(currentCell.column);
1128 final float centerY = getCenterYForRow(currentCell.row);
1129
1130 final Cell nextCell = pattern.get(numCircles);
1131 final float dx = percentageOfNextCircle *
1132 (getCenterXForColumn(nextCell.column) - centerX);
1133 final float dy = percentageOfNextCircle *
1134 (getCenterYForRow(nextCell.row) - centerY);
1135 mInProgressX = centerX + dx;
1136 mInProgressY = centerY + dy;
1137 }
1138 // TODO: Infinite loop here...
1139 invalidate();
1140 }
1141
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001142 final Path currentPath = mCurrentPath;
1143 currentPath.rewind();
1144
Jim Miller08a975e2011-06-22 14:07:44 -07001145 // draw the circles
Jim Miller08a975e2011-06-22 14:07:44 -07001146 for (int i = 0; i < 3; i++) {
Jorim Jaggic1581972014-08-07 22:15:48 +02001147 float centerY = getCenterYForRow(i);
Jim Miller08a975e2011-06-22 14:07:44 -07001148 for (int j = 0; j < 3; j++) {
Jorim Jaggic1581972014-08-07 22:15:48 +02001149 CellState cellState = mCellStates[i][j];
1150 float centerX = getCenterXForColumn(j);
Jorim Jaggi613f55f2015-07-16 15:30:10 -07001151 float translationY = cellState.translationY;
Jorim Jaggi613f55f2015-07-16 15:30:10 -07001152
Alain Vongsouvanh88a2a612016-11-18 13:27:16 -08001153 if (mUseLockPatternDrawable) {
1154 drawCellDrawable(canvas, i, j, cellState.radius, drawLookup[i][j]);
1155 } else {
1156 if (isHardwareAccelerated() && cellState.hwAnimating) {
John Reck32f140aa62018-10-04 15:08:24 -07001157 RecordingCanvas recordingCanvas = (RecordingCanvas) canvas;
1158 recordingCanvas.drawCircle(cellState.hwCenterX, cellState.hwCenterY,
Alain Vongsouvanh88a2a612016-11-18 13:27:16 -08001159 cellState.hwRadius, cellState.hwPaint);
1160 } else {
1161 drawCircle(canvas, (int) centerX, (int) centerY + translationY,
1162 cellState.radius, drawLookup[i][j], cellState.alpha);
1163 }
Jorim Jaggi613f55f2015-07-16 15:30:10 -07001164 }
Jim Miller08a975e2011-06-22 14:07:44 -07001165 }
1166 }
1167
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001168 // TODO: the path should be created and cached every time we hit-detect a cell
1169 // only the last segment of the path should be computed here
Adrian Roose2d71e42014-01-08 15:32:10 -08001170 // draw the path of the pattern (unless we are in stealth mode)
1171 final boolean drawPath = !mInStealthMode;
Jim Miller08a975e2011-06-22 14:07:44 -07001172
Jim Miller08a975e2011-06-22 14:07:44 -07001173 if (drawPath) {
Jorim Jaggic1581972014-08-07 22:15:48 +02001174 mPathPaint.setColor(getCurrentColor(true /* partOfPattern */));
Jim Miller08a975e2011-06-22 14:07:44 -07001175
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001176 boolean anyCircles = false;
Jorim Jaggic1581972014-08-07 22:15:48 +02001177 float lastX = 0f;
1178 float lastY = 0f;
Vishwath Mohandcc1f1942018-01-24 16:54:25 -08001179 long elapsedRealtime = SystemClock.elapsedRealtime();
1180 for (int i = 0; i < count; i++) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001181 Cell cell = pattern.get(i);
1182
1183 // only draw the part of the pattern stored in
1184 // the lookup table (this is only different in the case
1185 // of animation).
1186 if (!drawLookup[cell.row][cell.column]) {
1187 break;
1188 }
1189 anyCircles = true;
1190
Vishwath Mohandcc1f1942018-01-24 16:54:25 -08001191 if (mLineFadeStart[i] == 0) {
1192 mLineFadeStart[i] = SystemClock.elapsedRealtime();
1193 }
1194
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001195 float centerX = getCenterXForColumn(cell.column);
1196 float centerY = getCenterYForRow(cell.row);
Jorim Jaggic1581972014-08-07 22:15:48 +02001197 if (i != 0) {
Lucas Dupinf84fa002018-10-24 10:12:29 -07001198 // Set this line segment to fade away animated.
Vishwath Mohandcc1f1942018-01-24 16:54:25 -08001199 int lineFadeVal = (int) Math.min((elapsedRealtime -
Lucas Dupinf84fa002018-10-24 10:12:29 -07001200 mLineFadeStart[i]) * LINE_FADE_ALPHA_MULTIPLIER, 255f);
Vishwath Mohandcc1f1942018-01-24 16:54:25 -08001201
Jorim Jaggic1581972014-08-07 22:15:48 +02001202 CellState state = mCellStates[cell.row][cell.column];
1203 currentPath.rewind();
1204 currentPath.moveTo(lastX, lastY);
1205 if (state.lineEndX != Float.MIN_VALUE && state.lineEndY != Float.MIN_VALUE) {
1206 currentPath.lineTo(state.lineEndX, state.lineEndY);
Vishwath Mohan8c8b9fb2018-02-16 12:13:48 -08001207 if (mFadePattern) {
1208 mPathPaint.setAlpha((int) 255 - lineFadeVal );
1209 } else {
1210 mPathPaint.setAlpha(255);
1211 }
Jorim Jaggic1581972014-08-07 22:15:48 +02001212 } else {
1213 currentPath.lineTo(centerX, centerY);
Vishwath Mohan8c8b9fb2018-02-16 12:13:48 -08001214 if (mFadePattern) {
1215 mPathPaint.setAlpha((int) 255 - lineFadeVal );
1216 } else {
1217 mPathPaint.setAlpha(255);
1218 }
Jorim Jaggic1581972014-08-07 22:15:48 +02001219 }
1220 canvas.drawPath(currentPath, mPathPaint);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001221 }
Jorim Jaggic1581972014-08-07 22:15:48 +02001222 lastX = centerX;
1223 lastY = centerY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001224 }
1225
Jorim Jaggic1581972014-08-07 22:15:48 +02001226 // draw last in progress section
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001227 if ((mPatternInProgress || mPatternDisplayMode == DisplayMode.Animate)
1228 && anyCircles) {
Jorim Jaggic1581972014-08-07 22:15:48 +02001229 currentPath.rewind();
1230 currentPath.moveTo(lastX, lastY);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001231 currentPath.lineTo(mInProgressX, mInProgressY);
Jorim Jaggic1581972014-08-07 22:15:48 +02001232
1233 mPathPaint.setAlpha((int) (calculateLastSegmentAlpha(
1234 mInProgressX, mInProgressY, lastX, lastY) * 255f));
1235 canvas.drawPath(currentPath, mPathPaint);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001236 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001237 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001238 }
1239
Jorim Jaggic1581972014-08-07 22:15:48 +02001240 private float calculateLastSegmentAlpha(float x, float y, float lastX, float lastY) {
1241 float diffX = x - lastX;
1242 float diffY = y - lastY;
1243 float dist = (float) Math.sqrt(diffX*diffX + diffY*diffY);
1244 float frac = dist/mSquareWidth;
1245 return Math.min(1f, Math.max(0f, (frac - 0.3f) * 4f));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001246 }
1247
Jorim Jaggic1581972014-08-07 22:15:48 +02001248 private int getCurrentColor(boolean partOfPattern) {
1249 if (!partOfPattern || mInStealthMode || mPatternInProgress) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001250 // unselected circle
Jorim Jaggic1581972014-08-07 22:15:48 +02001251 return mRegularColor;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001252 } else if (mPatternDisplayMode == DisplayMode.Wrong) {
1253 // the pattern is wrong
Jorim Jaggic1581972014-08-07 22:15:48 +02001254 return mErrorColor;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001255 } else if (mPatternDisplayMode == DisplayMode.Correct ||
1256 mPatternDisplayMode == DisplayMode.Animate) {
Jorim Jaggic1581972014-08-07 22:15:48 +02001257 return mSuccessColor;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001258 } else {
1259 throw new IllegalStateException("unknown display mode " + mPatternDisplayMode);
1260 }
Jorim Jaggic1581972014-08-07 22:15:48 +02001261 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001262
Jorim Jaggic1581972014-08-07 22:15:48 +02001263 /**
1264 * @param partOfPattern Whether this circle is part of the pattern.
1265 */
Jorim Jaggi613f55f2015-07-16 15:30:10 -07001266 private void drawCircle(Canvas canvas, float centerX, float centerY, float radius,
Jorim Jaggic1581972014-08-07 22:15:48 +02001267 boolean partOfPattern, float alpha) {
1268 mPaint.setColor(getCurrentColor(partOfPattern));
1269 mPaint.setAlpha((int) (alpha * 255));
Jorim Jaggi613f55f2015-07-16 15:30:10 -07001270 canvas.drawCircle(centerX, centerY, radius, mPaint);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001271 }
1272
Alain Vongsouvanh88a2a612016-11-18 13:27:16 -08001273 /**
1274 * @param partOfPattern Whether this circle is part of the pattern.
1275 */
1276 private void drawCellDrawable(Canvas canvas, int i, int j, float radius,
1277 boolean partOfPattern) {
1278 Rect dst = new Rect(
1279 (int) (mPaddingLeft + j * mSquareWidth),
1280 (int) (mPaddingTop + i * mSquareHeight),
1281 (int) (mPaddingLeft + (j + 1) * mSquareWidth),
1282 (int) (mPaddingTop + (i + 1) * mSquareHeight));
1283 float scale = radius / (mDotSize / 2);
1284
1285 // Only draw on this square with the appropriate scale.
1286 canvas.save();
1287 canvas.clipRect(dst);
1288 canvas.scale(scale, scale, dst.centerX(), dst.centerY());
1289 if (!partOfPattern || scale > 1) {
1290 mNotSelectedDrawable.draw(canvas);
1291 } else {
1292 mSelectedDrawable.draw(canvas);
1293 }
1294 canvas.restore();
1295 }
1296
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001297 @Override
1298 protected Parcelable onSaveInstanceState() {
1299 Parcelable superState = super.onSaveInstanceState();
Rich Canningsf64ec632019-02-21 12:40:36 -08001300 byte[] patternBytes = LockPatternUtils.patternToByteArray(mPattern);
1301 String patternString = patternBytes != null ? new String(patternBytes) : null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001302 return new SavedState(superState,
Rich Canningsf64ec632019-02-21 12:40:36 -08001303 patternString,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001304 mPatternDisplayMode.ordinal(),
Jim Milleraef555b2011-10-12 16:41:30 -07001305 mInputEnabled, mInStealthMode, mEnableHapticFeedback);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001306 }
1307
1308 @Override
1309 protected void onRestoreInstanceState(Parcelable state) {
1310 final SavedState ss = (SavedState) state;
1311 super.onRestoreInstanceState(ss.getSuperState());
1312 setPattern(
1313 DisplayMode.Correct,
1314 LockPatternUtils.stringToPattern(ss.getSerializedPattern()));
1315 mPatternDisplayMode = DisplayMode.values()[ss.getDisplayMode()];
1316 mInputEnabled = ss.isInputEnabled();
1317 mInStealthMode = ss.isInStealthMode();
Jim Milleraef555b2011-10-12 16:41:30 -07001318 mEnableHapticFeedback = ss.isTactileFeedbackEnabled();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001319 }
1320
1321 /**
1322 * The parecelable for saving and restoring a lock pattern view.
1323 */
1324 private static class SavedState extends BaseSavedState {
1325
1326 private final String mSerializedPattern;
1327 private final int mDisplayMode;
1328 private final boolean mInputEnabled;
1329 private final boolean mInStealthMode;
1330 private final boolean mTactileFeedbackEnabled;
1331
1332 /**
1333 * Constructor called from {@link LockPatternView#onSaveInstanceState()}
1334 */
Andrei Onea15884392019-03-22 17:28:11 +00001335 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001336 private SavedState(Parcelable superState, String serializedPattern, int displayMode,
1337 boolean inputEnabled, boolean inStealthMode, boolean tactileFeedbackEnabled) {
1338 super(superState);
1339 mSerializedPattern = serializedPattern;
1340 mDisplayMode = displayMode;
1341 mInputEnabled = inputEnabled;
1342 mInStealthMode = inStealthMode;
1343 mTactileFeedbackEnabled = tactileFeedbackEnabled;
1344 }
1345
1346 /**
1347 * Constructor called from {@link #CREATOR}
1348 */
Andrei Onea15884392019-03-22 17:28:11 +00001349 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001350 private SavedState(Parcel in) {
1351 super(in);
1352 mSerializedPattern = in.readString();
1353 mDisplayMode = in.readInt();
1354 mInputEnabled = (Boolean) in.readValue(null);
1355 mInStealthMode = (Boolean) in.readValue(null);
1356 mTactileFeedbackEnabled = (Boolean) in.readValue(null);
1357 }
Jim Millerbf1259b2010-03-31 18:15:27 -07001358
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001359 public String getSerializedPattern() {
1360 return mSerializedPattern;
1361 }
1362
1363 public int getDisplayMode() {
1364 return mDisplayMode;
1365 }
1366
1367 public boolean isInputEnabled() {
1368 return mInputEnabled;
1369 }
1370
1371 public boolean isInStealthMode() {
1372 return mInStealthMode;
1373 }
1374
1375 public boolean isTactileFeedbackEnabled(){
1376 return mTactileFeedbackEnabled;
1377 }
1378
1379 @Override
1380 public void writeToParcel(Parcel dest, int flags) {
1381 super.writeToParcel(dest, flags);
1382 dest.writeString(mSerializedPattern);
1383 dest.writeInt(mDisplayMode);
1384 dest.writeValue(mInputEnabled);
1385 dest.writeValue(mInStealthMode);
1386 dest.writeValue(mTactileFeedbackEnabled);
1387 }
1388
Paul Crowleya97227a2014-10-23 10:25:41 +01001389 @SuppressWarnings({ "unused", "hiding" }) // Found using reflection
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001390 public static final Parcelable.Creator<SavedState> CREATOR =
1391 new Creator<SavedState>() {
Jim Miller240a2952015-06-10 16:55:24 -07001392 @Override
1393 public SavedState createFromParcel(Parcel in) {
1394 return new SavedState(in);
1395 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001396
Jim Miller240a2952015-06-10 16:55:24 -07001397 @Override
1398 public SavedState[] newArray(int size) {
1399 return new SavedState[size];
1400 }
1401 };
1402 }
1403
1404 private final class PatternExploreByTouchHelper extends ExploreByTouchHelper {
1405 private Rect mTempRect = new Rect();
Phil Weaverf5d677d2017-05-30 17:26:59 -07001406 private final SparseArray<VirtualViewContainer> mItems = new SparseArray<>();
Jim Miller240a2952015-06-10 16:55:24 -07001407
1408 class VirtualViewContainer {
1409 public VirtualViewContainer(CharSequence description) {
1410 this.description = description;
1411 }
1412 CharSequence description;
1413 };
1414
1415 public PatternExploreByTouchHelper(View forView) {
1416 super(forView);
Phil Weaverf5d677d2017-05-30 17:26:59 -07001417 for (int i = VIRTUAL_BASE_VIEW_ID; i < VIRTUAL_BASE_VIEW_ID + 9; i++) {
1418 mItems.put(i, new VirtualViewContainer(getTextForVirtualView(i)));
1419 }
Jim Miller240a2952015-06-10 16:55:24 -07001420 }
1421
1422 @Override
1423 protected int getVirtualViewAt(float x, float y) {
1424 // This must use the same hit logic for the screen to ensure consistency whether
1425 // accessibility is on or off.
1426 int id = getVirtualViewIdForHit(x, y);
1427 return id;
1428 }
1429
1430 @Override
1431 protected void getVisibleVirtualViews(IntArray virtualViewIds) {
1432 if (DEBUG_A11Y) Log.v(TAG, "getVisibleVirtualViews(len=" + virtualViewIds.size() + ")");
Adrian Roosd2def942015-07-27 13:49:53 -07001433 if (!mPatternInProgress) {
1434 return;
1435 }
Jim Miller240a2952015-06-10 16:55:24 -07001436 for (int i = VIRTUAL_BASE_VIEW_ID; i < VIRTUAL_BASE_VIEW_ID + 9; i++) {
Jim Miller240a2952015-06-10 16:55:24 -07001437 // Add all views. As views are added to the pattern, we remove them
1438 // from notification by making them non-clickable below.
1439 virtualViewIds.add(i);
1440 }
1441 }
1442
1443 @Override
1444 protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
1445 if (DEBUG_A11Y) Log.v(TAG, "onPopulateEventForVirtualView(" + virtualViewId + ")");
1446 // Announce this view
Phil Weaverf5d677d2017-05-30 17:26:59 -07001447 VirtualViewContainer container = mItems.get(virtualViewId);
1448 if (container != null) {
1449 event.getText().add(container.description);
Jim Miller240a2952015-06-10 16:55:24 -07001450 }
1451 }
1452
1453 @Override
Adrian Roosd2def942015-07-27 13:49:53 -07001454 public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
1455 super.onPopulateAccessibilityEvent(host, event);
1456 if (!mPatternInProgress) {
1457 CharSequence contentDescription = getContext().getText(
1458 com.android.internal.R.string.lockscreen_access_pattern_area);
1459 event.setContentDescription(contentDescription);
1460 }
1461 }
1462
1463 @Override
Jim Miller240a2952015-06-10 16:55:24 -07001464 protected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfo node) {
1465 if (DEBUG_A11Y) Log.v(TAG, "onPopulateNodeForVirtualView(view=" + virtualViewId + ")");
1466
1467 // Node and event text and content descriptions are usually
1468 // identical, so we'll use the exact same string as before.
1469 node.setText(getTextForVirtualView(virtualViewId));
1470 node.setContentDescription(getTextForVirtualView(virtualViewId));
1471
Adrian Roosd2def942015-07-27 13:49:53 -07001472 if (mPatternInProgress) {
1473 node.setFocusable(true);
1474
1475 if (isClickable(virtualViewId)) {
1476 // Mark this node of interest by making it clickable.
1477 node.addAction(AccessibilityAction.ACTION_CLICK);
1478 node.setClickable(isClickable(virtualViewId));
1479 }
Jim Miller240a2952015-06-10 16:55:24 -07001480 }
1481
1482 // Compute bounds for this object
1483 final Rect bounds = getBoundsForVirtualView(virtualViewId);
1484 if (DEBUG_A11Y) Log.v(TAG, "bounds:" + bounds.toString());
1485 node.setBoundsInParent(bounds);
1486 }
1487
1488 private boolean isClickable(int virtualViewId) {
1489 // Dots are clickable if they're not part of the current pattern.
1490 if (virtualViewId != ExploreByTouchHelper.INVALID_ID) {
1491 int row = (virtualViewId - VIRTUAL_BASE_VIEW_ID) / 3;
1492 int col = (virtualViewId - VIRTUAL_BASE_VIEW_ID) % 3;
1493 return !mPatternDrawLookup[row][col];
1494 }
1495 return false;
1496 }
1497
1498 @Override
1499 protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
1500 Bundle arguments) {
1501 if (DEBUG_A11Y) Log.v(TAG, "onPerformActionForVirtualView(id=" + virtualViewId
1502 + ", action=" + action);
1503 switch (action) {
1504 case AccessibilityNodeInfo.ACTION_CLICK:
1505 // Click handling should be consistent with
1506 // onTouchEvent(). This ensures that the view works the
1507 // same whether accessibility is turned on or off.
1508 return onItemClicked(virtualViewId);
1509 default:
1510 if (DEBUG_A11Y) Log.v(TAG, "*** action not handled in "
1511 + "onPerformActionForVirtualView(viewId="
1512 + virtualViewId + "action=" + action + ")");
1513 }
1514 return false;
1515 }
1516
1517 boolean onItemClicked(int index) {
1518 if (DEBUG_A11Y) Log.v(TAG, "onItemClicked(" + index + ")");
1519
1520 // Since the item's checked state is exposed to accessibility
1521 // services through its AccessibilityNodeInfo, we need to invalidate
1522 // the item's virtual view. At some point in the future, the
1523 // framework will obtain an updated version of the virtual view.
1524 invalidateVirtualView(index);
1525
1526 // We need to let the framework know what type of event
1527 // happened. Accessibility services may use this event to provide
1528 // appropriate feedback to the user.
1529 sendEventForVirtualView(index, AccessibilityEvent.TYPE_VIEW_CLICKED);
1530
1531 return true;
1532 }
1533
1534 private Rect getBoundsForVirtualView(int virtualViewId) {
1535 int ordinal = virtualViewId - VIRTUAL_BASE_VIEW_ID;
1536 final Rect bounds = mTempRect;
1537 final int row = ordinal / 3;
1538 final int col = ordinal % 3;
1539 final CellState cell = mCellStates[row][col];
1540 float centerX = getCenterXForColumn(col);
1541 float centerY = getCenterYForRow(row);
1542 float cellheight = mSquareHeight * mHitFactor * 0.5f;
1543 float cellwidth = mSquareWidth * mHitFactor * 0.5f;
Jim Miller240a2952015-06-10 16:55:24 -07001544 bounds.left = (int) (centerX - cellwidth);
1545 bounds.right = (int) (centerX + cellwidth);
1546 bounds.top = (int) (centerY - cellheight);
1547 bounds.bottom = (int) (centerY + cellheight);
1548 return bounds;
1549 }
1550
Jim Miller240a2952015-06-10 16:55:24 -07001551 private CharSequence getTextForVirtualView(int virtualViewId) {
1552 final Resources res = getResources();
Phil Weaver385912e2017-02-10 10:06:56 -08001553 return res.getString(R.string.lockscreen_access_pattern_cell_added_verbose,
1554 virtualViewId);
Jim Miller240a2952015-06-10 16:55:24 -07001555 }
1556
1557 /**
1558 * Helper method to find which cell a point maps to
1559 *
1560 * if there's no hit.
1561 * @param x touch position x
1562 * @param y touch position y
1563 * @return VIRTUAL_BASE_VIEW_ID+id or 0 if no view was hit
1564 */
1565 private int getVirtualViewIdForHit(float x, float y) {
1566 final int rowHit = getRowHit(y);
1567 if (rowHit < 0) {
1568 return ExploreByTouchHelper.INVALID_ID;
1569 }
1570 final int columnHit = getColumnHit(x);
1571 if (columnHit < 0) {
1572 return ExploreByTouchHelper.INVALID_ID;
1573 }
1574 boolean dotAvailable = mPatternDrawLookup[rowHit][columnHit];
1575 int dotId = (rowHit * 3 + columnHit) + VIRTUAL_BASE_VIEW_ID;
1576 int view = dotAvailable ? dotId : ExploreByTouchHelper.INVALID_ID;
1577 if (DEBUG_A11Y) Log.v(TAG, "getVirtualViewIdForHit(" + x + "," + y + ") => "
1578 + view + "avail =" + dotAvailable);
1579 return view;
1580 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001581 }
1582}