blob: 26cb4e5bc5a372bbf616ffd5d9b7559ef385a5b6 [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
19
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020import android.content.Context;
Jim Millerbf1259b2010-03-31 18:15:27 -070021import android.content.res.TypedArray;
Karl Rosaen6a109b42009-07-10 16:08:56 -070022import android.graphics.Bitmap;
23import android.graphics.BitmapFactory;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080024import android.graphics.Canvas;
25import android.graphics.Color;
26import android.graphics.Matrix;
27import android.graphics.Paint;
28import android.graphics.Path;
29import android.graphics.Rect;
Karl Rosaen6a109b42009-07-10 16:08:56 -070030import android.os.Debug;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031import android.os.Parcel;
32import android.os.Parcelable;
33import android.os.SystemClock;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034import android.util.AttributeSet;
Jim Milleraef555b2011-10-12 16:41:30 -070035import android.view.HapticFeedbackConstants;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080036import android.view.MotionEvent;
37import android.view.View;
Svetoslav Ganov530d9f12011-10-04 14:51:25 -070038import android.view.accessibility.AccessibilityManager;
39
40import com.android.internal.R;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041
42import java.util.ArrayList;
43import java.util.List;
44
45/**
46 * Displays and detects the user's unlock attempt, which is a drag of a finger
47 * across 9 regions of the screen.
48 *
49 * Is also capable of displaying a static pattern in "in progress", "wrong" or
50 * "correct" states.
51 */
52public class LockPatternView extends View {
Jim Millerbf1259b2010-03-31 18:15:27 -070053 // Aspect to use when rendering this view
54 private static final int ASPECT_SQUARE = 0; // View will be the minimum of width/height
55 private static final int ASPECT_LOCK_WIDTH = 1; // Fixed width; height will be minimum of (w,h)
56 private static final int ASPECT_LOCK_HEIGHT = 2; // Fixed height; width will be minimum of (w,h)
57
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080058 private static final boolean PROFILE_DRAWING = false;
59 private boolean mDrawingProfilingStarted = false;
60
61 private Paint mPaint = new Paint();
62 private Paint mPathPaint = new Paint();
63
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080064 /**
65 * How many milliseconds we spend animating each circle of a lock pattern
66 * if the animating mode is set. The entire animation should take this
67 * constant * the length of the pattern to complete.
68 */
69 private static final int MILLIS_PER_CIRCLE_ANIMATING = 700;
70
Jim Miller0caa3772013-03-07 18:44:32 -080071 /**
72 * This can be used to avoid updating the display for very small motions or noisy panels.
73 * It didn't seem to have much impact on the devices tested, so currently set to 0.
74 */
75 private static final float DRAG_THRESHHOLD = 0.0f;
76
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080077 private OnPatternListener mOnPatternListener;
78 private ArrayList<Cell> mPattern = new ArrayList<Cell>(9);
79
80 /**
81 * Lookup table for the circles of the pattern we are currently drawing.
82 * This will be the cells of the complete pattern unless we are animating,
83 * in which case we use this to hold the cells we are drawing for the in
84 * progress animation.
85 */
86 private boolean[][] mPatternDrawLookup = new boolean[3][3];
87
88 /**
89 * the in progress point:
90 * - during interaction: where the user's finger is
91 * - during animation: the current tip of the animating line
92 */
93 private float mInProgressX = -1;
94 private float mInProgressY = -1;
95
96 private long mAnimatingPeriodStart;
97
98 private DisplayMode mPatternDisplayMode = DisplayMode.Correct;
99 private boolean mInputEnabled = true;
100 private boolean mInStealthMode = false;
Jim Milleraef555b2011-10-12 16:41:30 -0700101 private boolean mEnableHapticFeedback = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800102 private boolean mPatternInProgress = false;
103
Jim Miller08a975e2011-06-22 14:07:44 -0700104 private float mDiameterFactor = 0.10f; // TODO: move to attrs
105 private final int mStrokeAlpha = 128;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800106 private float mHitFactor = 0.6f;
107
108 private float mSquareWidth;
109 private float mSquareHeight;
110
111 private Bitmap mBitmapBtnDefault;
112 private Bitmap mBitmapBtnTouched;
113 private Bitmap mBitmapCircleDefault;
114 private Bitmap mBitmapCircleGreen;
115 private Bitmap mBitmapCircleRed;
116
117 private Bitmap mBitmapArrowGreenUp;
118 private Bitmap mBitmapArrowRedUp;
119
120 private final Path mCurrentPath = new Path();
121 private final Rect mInvalidate = new Rect();
Jim Miller9ddfeb82013-04-10 18:15:30 -0700122 private final Rect mTmpInvalidateRect = new Rect();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800123
124 private int mBitmapWidth;
125 private int mBitmapHeight;
Jim Millerbf1259b2010-03-31 18:15:27 -0700126
Jim Millerbf1259b2010-03-31 18:15:27 -0700127 private int mAspect;
Romain Guy5b3b3522010-10-27 18:57:51 -0700128 private final Matrix mArrowMatrix = new Matrix();
Jim Miller0d244192011-06-16 17:31:21 -0700129 private final Matrix mCircleMatrix = new Matrix();
Jim Millerbf1259b2010-03-31 18:15:27 -0700130
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800131 /**
132 * Represents a cell in the 3 X 3 matrix of the unlock pattern view.
133 */
134 public static class Cell {
135 int row;
136 int column;
137
138 // keep # objects limited to 9
139 static Cell[][] sCells = new Cell[3][3];
140 static {
141 for (int i = 0; i < 3; i++) {
142 for (int j = 0; j < 3; j++) {
143 sCells[i][j] = new Cell(i, j);
144 }
145 }
146 }
147
148 /**
149 * @param row The row of the cell.
150 * @param column The column of the cell.
151 */
152 private Cell(int row, int column) {
153 checkRange(row, column);
154 this.row = row;
155 this.column = column;
156 }
157
158 public int getRow() {
159 return row;
160 }
161
162 public int getColumn() {
163 return column;
164 }
165
166 /**
167 * @param row The row of the cell.
168 * @param column The column of the cell.
169 */
170 public static synchronized Cell of(int row, int column) {
171 checkRange(row, column);
172 return sCells[row][column];
173 }
174
175 private static void checkRange(int row, int column) {
176 if (row < 0 || row > 2) {
177 throw new IllegalArgumentException("row must be in range 0-2");
178 }
179 if (column < 0 || column > 2) {
180 throw new IllegalArgumentException("column must be in range 0-2");
181 }
182 }
183
184 public String toString() {
185 return "(row=" + row + ",clmn=" + column + ")";
186 }
187 }
188
189 /**
190 * How to display the current pattern.
191 */
192 public enum DisplayMode {
193
194 /**
195 * The pattern drawn is correct (i.e draw it in a friendly color)
196 */
197 Correct,
198
199 /**
200 * Animate the pattern (for demo, and help).
201 */
202 Animate,
203
204 /**
205 * The pattern is wrong (i.e draw a foreboding color)
206 */
207 Wrong
208 }
209
210 /**
211 * The call back interface for detecting patterns entered by the user.
212 */
213 public static interface OnPatternListener {
214
215 /**
216 * A new pattern has begun.
217 */
218 void onPatternStart();
219
220 /**
221 * The pattern was cleared.
222 */
223 void onPatternCleared();
224
225 /**
Jim Miller41e8dc02009-09-29 14:16:21 -0700226 * The user extended the pattern currently being drawn by one cell.
227 * @param pattern The pattern with newly added cell.
228 */
229 void onPatternCellAdded(List<Cell> pattern);
230
231 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800232 * A pattern was detected from the user.
233 * @param pattern The pattern.
234 */
235 void onPatternDetected(List<Cell> pattern);
236 }
237
238 public LockPatternView(Context context) {
239 this(context, null);
240 }
241
242 public LockPatternView(Context context, AttributeSet attrs) {
243 super(context, attrs);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800244
Jim Millerbf1259b2010-03-31 18:15:27 -0700245 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LockPatternView);
246
247 final String aspect = a.getString(R.styleable.LockPatternView_aspect);
248
249 if ("square".equals(aspect)) {
250 mAspect = ASPECT_SQUARE;
251 } else if ("lock_width".equals(aspect)) {
252 mAspect = ASPECT_LOCK_WIDTH;
253 } else if ("lock_height".equals(aspect)) {
254 mAspect = ASPECT_LOCK_HEIGHT;
255 } else {
256 mAspect = ASPECT_SQUARE;
257 }
258
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800259 setClickable(true);
260
261 mPathPaint.setAntiAlias(true);
262 mPathPaint.setDither(true);
263 mPathPaint.setColor(Color.WHITE); // TODO this should be from the style
Jim Miller08a975e2011-06-22 14:07:44 -0700264 mPathPaint.setAlpha(mStrokeAlpha);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800265 mPathPaint.setStyle(Paint.Style.STROKE);
266 mPathPaint.setStrokeJoin(Paint.Join.ROUND);
267 mPathPaint.setStrokeCap(Paint.Cap.ROUND);
268
269 // lot's of bitmaps!
Jim Miller08a975e2011-06-22 14:07:44 -0700270 mBitmapBtnDefault = getBitmapFor(R.drawable.btn_code_lock_default_holo);
271 mBitmapBtnTouched = getBitmapFor(R.drawable.btn_code_lock_touched_holo);
272 mBitmapCircleDefault = getBitmapFor(R.drawable.indicator_code_lock_point_area_default_holo);
273 mBitmapCircleGreen = getBitmapFor(R.drawable.indicator_code_lock_point_area_green_holo);
274 mBitmapCircleRed = getBitmapFor(R.drawable.indicator_code_lock_point_area_red_holo);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800275
276 mBitmapArrowGreenUp = getBitmapFor(R.drawable.indicator_code_lock_drag_direction_green_up);
277 mBitmapArrowRedUp = getBitmapFor(R.drawable.indicator_code_lock_drag_direction_red_up);
278
Jim Miller0d244192011-06-16 17:31:21 -0700279 // bitmaps have the size of the largest bitmap in this group
280 final Bitmap bitmaps[] = { mBitmapBtnDefault, mBitmapBtnTouched, mBitmapCircleDefault,
281 mBitmapCircleGreen, mBitmapCircleRed };
282
283 for (Bitmap bitmap : bitmaps) {
284 mBitmapWidth = Math.max(mBitmapWidth, bitmap.getWidth());
285 mBitmapHeight = Math.max(mBitmapHeight, bitmap.getHeight());
286 }
Jim Miller85d63002009-09-30 02:50:02 -0700287
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800288 }
289
290 private Bitmap getBitmapFor(int resId) {
291 return BitmapFactory.decodeResource(getContext().getResources(), resId);
292 }
293
294 /**
295 * @return Whether the view is in stealth mode.
296 */
297 public boolean isInStealthMode() {
298 return mInStealthMode;
299 }
300
301 /**
302 * @return Whether the view has tactile feedback enabled.
303 */
304 public boolean isTactileFeedbackEnabled() {
Jim Milleraef555b2011-10-12 16:41:30 -0700305 return mEnableHapticFeedback;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800306 }
307
308 /**
309 * Set whether the view is in stealth mode. If true, there will be no
310 * visible feedback as the user enters the pattern.
311 *
312 * @param inStealthMode Whether in stealth mode.
313 */
314 public void setInStealthMode(boolean inStealthMode) {
315 mInStealthMode = inStealthMode;
316 }
317
318 /**
319 * Set whether the view will use tactile feedback. If true, there will be
320 * tactile feedback as the user enters the pattern.
321 *
322 * @param tactileFeedbackEnabled Whether tactile feedback is enabled
323 */
324 public void setTactileFeedbackEnabled(boolean tactileFeedbackEnabled) {
Jim Milleraef555b2011-10-12 16:41:30 -0700325 mEnableHapticFeedback = tactileFeedbackEnabled;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800326 }
327
328 /**
329 * Set the call back for pattern detection.
330 * @param onPatternListener The call back.
331 */
332 public void setOnPatternListener(
333 OnPatternListener onPatternListener) {
334 mOnPatternListener = onPatternListener;
335 }
336
337 /**
338 * Set the pattern explicitely (rather than waiting for the user to input
339 * a pattern).
340 * @param displayMode How to display the pattern.
341 * @param pattern The pattern.
342 */
343 public void setPattern(DisplayMode displayMode, List<Cell> pattern) {
344 mPattern.clear();
345 mPattern.addAll(pattern);
346 clearPatternDrawLookup();
347 for (Cell cell : pattern) {
348 mPatternDrawLookup[cell.getRow()][cell.getColumn()] = true;
349 }
350
351 setDisplayMode(displayMode);
352 }
353
354 /**
355 * Set the display mode of the current pattern. This can be useful, for
356 * instance, after detecting a pattern to tell this view whether change the
357 * in progress result to correct or wrong.
358 * @param displayMode The display mode.
359 */
360 public void setDisplayMode(DisplayMode displayMode) {
361 mPatternDisplayMode = displayMode;
362 if (displayMode == DisplayMode.Animate) {
363 if (mPattern.size() == 0) {
364 throw new IllegalStateException("you must have a pattern to "
365 + "animate if you want to set the display mode to animate");
366 }
367 mAnimatingPeriodStart = SystemClock.elapsedRealtime();
368 final Cell first = mPattern.get(0);
369 mInProgressX = getCenterXForColumn(first.getColumn());
370 mInProgressY = getCenterYForRow(first.getRow());
371 clearPatternDrawLookup();
372 }
373 invalidate();
374 }
375
Svetoslav Ganov530d9f12011-10-04 14:51:25 -0700376 private void notifyCellAdded() {
alanve303c5c2012-10-03 13:15:14 -0700377 sendAccessEvent(R.string.lockscreen_access_pattern_cell_added);
Svetoslav Ganov530d9f12011-10-04 14:51:25 -0700378 if (mOnPatternListener != null) {
379 mOnPatternListener.onPatternCellAdded(mPattern);
380 }
Svetoslav Ganov530d9f12011-10-04 14:51:25 -0700381 }
382
383 private void notifyPatternStarted() {
alanve303c5c2012-10-03 13:15:14 -0700384 sendAccessEvent(R.string.lockscreen_access_pattern_start);
Svetoslav Ganov530d9f12011-10-04 14:51:25 -0700385 if (mOnPatternListener != null) {
386 mOnPatternListener.onPatternStart();
387 }
Svetoslav Ganov530d9f12011-10-04 14:51:25 -0700388 }
389
390 private void notifyPatternDetected() {
alanve303c5c2012-10-03 13:15:14 -0700391 sendAccessEvent(R.string.lockscreen_access_pattern_detected);
Svetoslav Ganov530d9f12011-10-04 14:51:25 -0700392 if (mOnPatternListener != null) {
393 mOnPatternListener.onPatternDetected(mPattern);
394 }
Svetoslav Ganov530d9f12011-10-04 14:51:25 -0700395 }
396
397 private void notifyPatternCleared() {
alanve303c5c2012-10-03 13:15:14 -0700398 sendAccessEvent(R.string.lockscreen_access_pattern_cleared);
Svetoslav Ganov530d9f12011-10-04 14:51:25 -0700399 if (mOnPatternListener != null) {
400 mOnPatternListener.onPatternCleared();
401 }
Svetoslav Ganov530d9f12011-10-04 14:51:25 -0700402 }
403
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800404 /**
405 * Clear the pattern.
406 */
407 public void clearPattern() {
408 resetPattern();
409 }
410
411 /**
412 * Reset all pattern state.
413 */
414 private void resetPattern() {
415 mPattern.clear();
416 clearPatternDrawLookup();
417 mPatternDisplayMode = DisplayMode.Correct;
418 invalidate();
419 }
420
421 /**
422 * Clear the pattern lookup table.
423 */
424 private void clearPatternDrawLookup() {
425 for (int i = 0; i < 3; i++) {
426 for (int j = 0; j < 3; j++) {
427 mPatternDrawLookup[i][j] = false;
428 }
429 }
430 }
431
432 /**
433 * Disable input (for instance when displaying a message that will
434 * timeout so user doesn't get view into messy state).
435 */
436 public void disableInput() {
437 mInputEnabled = false;
438 }
439
440 /**
441 * Enable input.
442 */
443 public void enableInput() {
444 mInputEnabled = true;
445 }
446
447 @Override
448 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
449 final int width = w - mPaddingLeft - mPaddingRight;
450 mSquareWidth = width / 3.0f;
451
452 final int height = h - mPaddingTop - mPaddingBottom;
453 mSquareHeight = height / 3.0f;
454 }
455
Jim Miller0a075382010-12-15 09:28:56 -0800456 private int resolveMeasured(int measureSpec, int desired)
457 {
458 int result = 0;
459 int specSize = MeasureSpec.getSize(measureSpec);
460 switch (MeasureSpec.getMode(measureSpec)) {
461 case MeasureSpec.UNSPECIFIED:
462 result = desired;
463 break;
464 case MeasureSpec.AT_MOST:
Peter Ng7bc60ab2011-11-09 15:13:51 -0800465 result = Math.max(specSize, desired);
Jim Miller0a075382010-12-15 09:28:56 -0800466 break;
467 case MeasureSpec.EXACTLY:
468 default:
Philip Milne1fd16372011-06-21 14:57:47 -0700469 result = specSize;
Jim Miller0a075382010-12-15 09:28:56 -0800470 }
471 return result;
472 }
473
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800474 @Override
Jim Miller0d244192011-06-16 17:31:21 -0700475 protected int getSuggestedMinimumWidth() {
476 // View should be large enough to contain 3 side-by-side target bitmaps
477 return 3 * mBitmapWidth;
478 }
479
480 @Override
481 protected int getSuggestedMinimumHeight() {
482 // View should be large enough to contain 3 side-by-side target bitmaps
483 return 3 * mBitmapWidth;
484 }
485
486 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800487 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Jim Miller0d244192011-06-16 17:31:21 -0700488 final int minimumWidth = getSuggestedMinimumWidth();
489 final int minimumHeight = getSuggestedMinimumHeight();
Jim Miller0a075382010-12-15 09:28:56 -0800490 int viewWidth = resolveMeasured(widthMeasureSpec, minimumWidth);
491 int viewHeight = resolveMeasured(heightMeasureSpec, minimumHeight);
492
Jim Millerbf1259b2010-03-31 18:15:27 -0700493 switch (mAspect) {
494 case ASPECT_SQUARE:
Jim Miller0d244192011-06-16 17:31:21 -0700495 viewWidth = viewHeight = Math.min(viewWidth, viewHeight);
Jim Millerbf1259b2010-03-31 18:15:27 -0700496 break;
497 case ASPECT_LOCK_WIDTH:
Jim Miller0d244192011-06-16 17:31:21 -0700498 viewHeight = Math.min(viewWidth, viewHeight);
Jim Millerbf1259b2010-03-31 18:15:27 -0700499 break;
500 case ASPECT_LOCK_HEIGHT:
Jim Miller0d244192011-06-16 17:31:21 -0700501 viewWidth = Math.min(viewWidth, viewHeight);
Jim Millerbf1259b2010-03-31 18:15:27 -0700502 break;
503 }
Jim Miller0a075382010-12-15 09:28:56 -0800504 // Log.v(TAG, "LockPatternView dimensions: " + viewWidth + "x" + viewHeight);
Jim Millerbf1259b2010-03-31 18:15:27 -0700505 setMeasuredDimension(viewWidth, viewHeight);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800506 }
507
508 /**
509 * Determines whether the point x, y will add a new point to the current
510 * pattern (in addition to finding the cell, also makes heuristic choices
511 * such as filling in gaps based on current pattern).
512 * @param x The x coordinate.
513 * @param y The y coordinate.
514 */
515 private Cell detectAndAddHit(float x, float y) {
516 final Cell cell = checkForNewHit(x, y);
517 if (cell != null) {
518
519 // check for gaps in existing pattern
520 Cell fillInGapCell = null;
521 final ArrayList<Cell> pattern = mPattern;
522 if (!pattern.isEmpty()) {
523 final Cell lastCell = pattern.get(pattern.size() - 1);
524 int dRow = cell.row - lastCell.row;
525 int dColumn = cell.column - lastCell.column;
526
527 int fillInRow = lastCell.row;
528 int fillInColumn = lastCell.column;
529
530 if (Math.abs(dRow) == 2 && Math.abs(dColumn) != 1) {
531 fillInRow = lastCell.row + ((dRow > 0) ? 1 : -1);
532 }
533
534 if (Math.abs(dColumn) == 2 && Math.abs(dRow) != 1) {
535 fillInColumn = lastCell.column + ((dColumn > 0) ? 1 : -1);
536 }
537
538 fillInGapCell = Cell.of(fillInRow, fillInColumn);
539 }
540
541 if (fillInGapCell != null &&
542 !mPatternDrawLookup[fillInGapCell.row][fillInGapCell.column]) {
543 addCellToPattern(fillInGapCell);
544 }
545 addCellToPattern(cell);
Jim Milleraef555b2011-10-12 16:41:30 -0700546 if (mEnableHapticFeedback) {
547 performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
548 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING
549 | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800550 }
551 return cell;
552 }
553 return null;
554 }
555
556 private void addCellToPattern(Cell newCell) {
557 mPatternDrawLookup[newCell.getRow()][newCell.getColumn()] = true;
558 mPattern.add(newCell);
Svetoslav Ganov530d9f12011-10-04 14:51:25 -0700559 notifyCellAdded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800560 }
561
562 // helper method to find which cell a point maps to
563 private Cell checkForNewHit(float x, float y) {
564
565 final int rowHit = getRowHit(y);
566 if (rowHit < 0) {
567 return null;
568 }
569 final int columnHit = getColumnHit(x);
570 if (columnHit < 0) {
571 return null;
572 }
573
574 if (mPatternDrawLookup[rowHit][columnHit]) {
575 return null;
576 }
577 return Cell.of(rowHit, columnHit);
578 }
579
580 /**
581 * Helper method to find the row that y falls into.
582 * @param y The y coordinate
583 * @return The row that y falls in, or -1 if it falls in no row.
584 */
585 private int getRowHit(float y) {
586
587 final float squareHeight = mSquareHeight;
588 float hitSize = squareHeight * mHitFactor;
589
590 float offset = mPaddingTop + (squareHeight - hitSize) / 2f;
591 for (int i = 0; i < 3; i++) {
592
593 final float hitTop = offset + squareHeight * i;
594 if (y >= hitTop && y <= hitTop + hitSize) {
595 return i;
596 }
597 }
598 return -1;
599 }
600
601 /**
602 * Helper method to find the column x fallis into.
603 * @param x The x coordinate.
604 * @return The column that x falls in, or -1 if it falls in no column.
605 */
606 private int getColumnHit(float x) {
607 final float squareWidth = mSquareWidth;
608 float hitSize = squareWidth * mHitFactor;
609
610 float offset = mPaddingLeft + (squareWidth - hitSize) / 2f;
611 for (int i = 0; i < 3; i++) {
612
613 final float hitLeft = offset + squareWidth * i;
614 if (x >= hitLeft && x <= hitLeft + hitSize) {
615 return i;
616 }
617 }
618 return -1;
619 }
620
621 @Override
Svetoslav Ganov530d9f12011-10-04 14:51:25 -0700622 public boolean onHoverEvent(MotionEvent event) {
623 if (AccessibilityManager.getInstance(mContext).isTouchExplorationEnabled()) {
624 final int action = event.getAction();
625 switch (action) {
626 case MotionEvent.ACTION_HOVER_ENTER:
627 event.setAction(MotionEvent.ACTION_DOWN);
628 break;
629 case MotionEvent.ACTION_HOVER_MOVE:
630 event.setAction(MotionEvent.ACTION_MOVE);
631 break;
632 case MotionEvent.ACTION_HOVER_EXIT:
633 event.setAction(MotionEvent.ACTION_UP);
634 break;
635 }
636 onTouchEvent(event);
637 event.setAction(action);
638 }
639 return super.onHoverEvent(event);
640 }
641
642 @Override
Jim Milleraced12f2011-06-17 16:09:46 -0700643 public boolean onTouchEvent(MotionEvent event) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800644 if (!mInputEnabled || !isEnabled()) {
645 return false;
646 }
647
Jim Milleraced12f2011-06-17 16:09:46 -0700648 switch(event.getAction()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800649 case MotionEvent.ACTION_DOWN:
Jim Milleraced12f2011-06-17 16:09:46 -0700650 handleActionDown(event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800651 return true;
652 case MotionEvent.ACTION_UP:
Jim Milleraced12f2011-06-17 16:09:46 -0700653 handleActionUp(event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800654 return true;
655 case MotionEvent.ACTION_MOVE:
Jim Milleraced12f2011-06-17 16:09:46 -0700656 handleActionMove(event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800657 return true;
658 case MotionEvent.ACTION_CANCEL:
Svetoslav Ganovc4842c12012-10-31 14:33:32 -0700659 if (mPatternInProgress) {
660 mPatternInProgress = false;
661 resetPattern();
662 notifyPatternCleared();
663 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800664 if (PROFILE_DRAWING) {
665 if (mDrawingProfilingStarted) {
666 Debug.stopMethodTracing();
667 mDrawingProfilingStarted = false;
668 }
669 }
670 return true;
671 }
672 return false;
673 }
674
Jim Milleraced12f2011-06-17 16:09:46 -0700675 private void handleActionMove(MotionEvent event) {
676 // Handle all recent motion events so we don't skip any cells even when the device
677 // is busy...
Jim Miller9ddfeb82013-04-10 18:15:30 -0700678 final float radius = (mSquareWidth * mDiameterFactor * 0.5f);
Jim Milleraced12f2011-06-17 16:09:46 -0700679 final int historySize = event.getHistorySize();
Jim Miller9ddfeb82013-04-10 18:15:30 -0700680 mTmpInvalidateRect.setEmpty();
Jim Miller0caa3772013-03-07 18:44:32 -0800681 boolean invalidateNow = false;
Jim Milleraced12f2011-06-17 16:09:46 -0700682 for (int i = 0; i < historySize + 1; i++) {
683 final float x = i < historySize ? event.getHistoricalX(i) : event.getX();
684 final float y = i < historySize ? event.getHistoricalY(i) : event.getY();
Jim Milleraced12f2011-06-17 16:09:46 -0700685 Cell hitCell = detectAndAddHit(x, y);
686 final int patternSize = mPattern.size();
Svetoslav Ganov530d9f12011-10-04 14:51:25 -0700687 if (hitCell != null && patternSize == 1) {
Jim Milleraced12f2011-06-17 16:09:46 -0700688 mPatternInProgress = true;
Svetoslav Ganov530d9f12011-10-04 14:51:25 -0700689 notifyPatternStarted();
Jim Milleraced12f2011-06-17 16:09:46 -0700690 }
691 // note current x and y for rubber banding of in progress patterns
692 final float dx = Math.abs(x - mInProgressX);
693 final float dy = Math.abs(y - mInProgressY);
Jim Miller0caa3772013-03-07 18:44:32 -0800694 if (dx > DRAG_THRESHHOLD || dy > DRAG_THRESHHOLD) {
695 invalidateNow = true;
Jim Milleraced12f2011-06-17 16:09:46 -0700696 }
Jim Miller0caa3772013-03-07 18:44:32 -0800697
698 if (mPatternInProgress && patternSize > 0) {
699 final ArrayList<Cell> pattern = mPattern;
700 final Cell lastCell = pattern.get(patternSize - 1);
Jim Miller9ddfeb82013-04-10 18:15:30 -0700701 float lastCellCenterX = getCenterXForColumn(lastCell.column);
702 float lastCellCenterY = getCenterYForRow(lastCell.row);
Jim Miller0caa3772013-03-07 18:44:32 -0800703
Jim Miller9ddfeb82013-04-10 18:15:30 -0700704 // Adjust for drawn segment from last cell to (x,y). Radius accounts for line width.
705 float left = Math.min(lastCellCenterX, x) - radius;
706 float right = Math.max(lastCellCenterX, x) + radius;
707 float top = Math.min(lastCellCenterY, y) - radius;
708 float bottom = Math.max(lastCellCenterY, y) + radius;
Jim Miller0caa3772013-03-07 18:44:32 -0800709
710 // Invalidate between the pattern's new cell and the pattern's previous cell
Jim Miller9ddfeb82013-04-10 18:15:30 -0700711 if (hitCell != null) {
Jim Miller0caa3772013-03-07 18:44:32 -0800712 final float width = mSquareWidth * 0.5f;
713 final float height = mSquareHeight * 0.5f;
Jim Miller9ddfeb82013-04-10 18:15:30 -0700714 final float hitCellCenterX = getCenterXForColumn(hitCell.column);
715 final float hitCellCenterY = getCenterYForRow(hitCell.row);
716
717 left = Math.min(hitCellCenterX - width, left);
718 right = Math.max(hitCellCenterX + width, right);
719 top = Math.min(hitCellCenterY - height, top);
720 bottom = Math.max(hitCellCenterY + height, bottom);
Jim Miller0caa3772013-03-07 18:44:32 -0800721 }
722
723 // Invalidate between the pattern's last cell and the previous location
Jim Miller9ddfeb82013-04-10 18:15:30 -0700724 mTmpInvalidateRect.union(Math.round(left), Math.round(top),
Jim Miller0caa3772013-03-07 18:44:32 -0800725 Math.round(right), Math.round(bottom));
726 }
727 }
728 mInProgressX = event.getX();
729 mInProgressY = event.getY();
730
731 // To save updates, we only invalidate if the user moved beyond a certain amount.
732 if (invalidateNow) {
Jim Miller9ddfeb82013-04-10 18:15:30 -0700733 mInvalidate.union(mTmpInvalidateRect);
734 invalidate(mInvalidate);
735 mInvalidate.set(mTmpInvalidateRect);
Jim Milleraced12f2011-06-17 16:09:46 -0700736 }
737 }
738
Svetoslav Ganov530d9f12011-10-04 14:51:25 -0700739 private void sendAccessEvent(int resId) {
alanve303c5c2012-10-03 13:15:14 -0700740 announceForAccessibility(mContext.getString(resId));
Svetoslav Ganov530d9f12011-10-04 14:51:25 -0700741 }
742
Jim Milleraced12f2011-06-17 16:09:46 -0700743 private void handleActionUp(MotionEvent event) {
744 // report pattern detected
Svetoslav Ganov530d9f12011-10-04 14:51:25 -0700745 if (!mPattern.isEmpty()) {
Jim Milleraced12f2011-06-17 16:09:46 -0700746 mPatternInProgress = false;
Svetoslav Ganov530d9f12011-10-04 14:51:25 -0700747 notifyPatternDetected();
Jim Milleraced12f2011-06-17 16:09:46 -0700748 invalidate();
749 }
750 if (PROFILE_DRAWING) {
751 if (mDrawingProfilingStarted) {
752 Debug.stopMethodTracing();
753 mDrawingProfilingStarted = false;
754 }
755 }
756 }
757
758 private void handleActionDown(MotionEvent event) {
759 resetPattern();
760 final float x = event.getX();
761 final float y = event.getY();
762 final Cell hitCell = detectAndAddHit(x, y);
Svetoslav Ganov530d9f12011-10-04 14:51:25 -0700763 if (hitCell != null) {
Jim Milleraced12f2011-06-17 16:09:46 -0700764 mPatternInProgress = true;
765 mPatternDisplayMode = DisplayMode.Correct;
Svetoslav Ganov530d9f12011-10-04 14:51:25 -0700766 notifyPatternStarted();
Svetoslav Ganovc4842c12012-10-31 14:33:32 -0700767 } else if (mPatternInProgress) {
Jim Milleraced12f2011-06-17 16:09:46 -0700768 mPatternInProgress = false;
Svetoslav Ganov530d9f12011-10-04 14:51:25 -0700769 notifyPatternCleared();
Jim Milleraced12f2011-06-17 16:09:46 -0700770 }
771 if (hitCell != null) {
772 final float startX = getCenterXForColumn(hitCell.column);
773 final float startY = getCenterYForRow(hitCell.row);
774
775 final float widthOffset = mSquareWidth / 2f;
776 final float heightOffset = mSquareHeight / 2f;
777
778 invalidate((int) (startX - widthOffset), (int) (startY - heightOffset),
779 (int) (startX + widthOffset), (int) (startY + heightOffset));
780 }
781 mInProgressX = x;
782 mInProgressY = y;
783 if (PROFILE_DRAWING) {
784 if (!mDrawingProfilingStarted) {
785 Debug.startMethodTracing("LockPatternDrawing");
786 mDrawingProfilingStarted = true;
787 }
788 }
789 }
790
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800791 private float getCenterXForColumn(int column) {
792 return mPaddingLeft + column * mSquareWidth + mSquareWidth / 2f;
793 }
794
795 private float getCenterYForRow(int row) {
796 return mPaddingTop + row * mSquareHeight + mSquareHeight / 2f;
797 }
798
799 @Override
800 protected void onDraw(Canvas canvas) {
801 final ArrayList<Cell> pattern = mPattern;
802 final int count = pattern.size();
803 final boolean[][] drawLookup = mPatternDrawLookup;
804
805 if (mPatternDisplayMode == DisplayMode.Animate) {
806
807 // figure out which circles to draw
808
809 // + 1 so we pause on complete pattern
810 final int oneCycle = (count + 1) * MILLIS_PER_CIRCLE_ANIMATING;
811 final int spotInCycle = (int) (SystemClock.elapsedRealtime() -
812 mAnimatingPeriodStart) % oneCycle;
813 final int numCircles = spotInCycle / MILLIS_PER_CIRCLE_ANIMATING;
814
815 clearPatternDrawLookup();
816 for (int i = 0; i < numCircles; i++) {
817 final Cell cell = pattern.get(i);
818 drawLookup[cell.getRow()][cell.getColumn()] = true;
819 }
820
821 // figure out in progress portion of ghosting line
822
823 final boolean needToUpdateInProgressPoint = numCircles > 0
824 && numCircles < count;
825
826 if (needToUpdateInProgressPoint) {
827 final float percentageOfNextCircle =
828 ((float) (spotInCycle % MILLIS_PER_CIRCLE_ANIMATING)) /
829 MILLIS_PER_CIRCLE_ANIMATING;
830
831 final Cell currentCell = pattern.get(numCircles - 1);
832 final float centerX = getCenterXForColumn(currentCell.column);
833 final float centerY = getCenterYForRow(currentCell.row);
834
835 final Cell nextCell = pattern.get(numCircles);
836 final float dx = percentageOfNextCircle *
837 (getCenterXForColumn(nextCell.column) - centerX);
838 final float dy = percentageOfNextCircle *
839 (getCenterYForRow(nextCell.row) - centerY);
840 mInProgressX = centerX + dx;
841 mInProgressY = centerY + dy;
842 }
843 // TODO: Infinite loop here...
844 invalidate();
845 }
846
847 final float squareWidth = mSquareWidth;
848 final float squareHeight = mSquareHeight;
849
850 float radius = (squareWidth * mDiameterFactor * 0.5f);
851 mPathPaint.setStrokeWidth(radius);
852
853 final Path currentPath = mCurrentPath;
854 currentPath.rewind();
855
Jim Miller08a975e2011-06-22 14:07:44 -0700856 // draw the circles
857 final int paddingTop = mPaddingTop;
858 final int paddingLeft = mPaddingLeft;
859
860 for (int i = 0; i < 3; i++) {
861 float topY = paddingTop + i * squareHeight;
862 //float centerY = mPaddingTop + i * mSquareHeight + (mSquareHeight / 2);
863 for (int j = 0; j < 3; j++) {
864 float leftX = paddingLeft + j * squareWidth;
865 drawCircle(canvas, (int) leftX, (int) topY, drawLookup[i][j]);
866 }
867 }
868
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800869 // TODO: the path should be created and cached every time we hit-detect a cell
870 // only the last segment of the path should be computed here
Adrian Roose2d71e42014-01-08 15:32:10 -0800871 // draw the path of the pattern (unless we are in stealth mode)
872 final boolean drawPath = !mInStealthMode;
Jim Miller08a975e2011-06-22 14:07:44 -0700873
Adrian Roose2d71e42014-01-08 15:32:10 -0800874 // draw the arrows associated with the path (unless we are in stealth mode)
Jim Miller08a975e2011-06-22 14:07:44 -0700875 boolean oldFlag = (mPaint.getFlags() & Paint.FILTER_BITMAP_FLAG) != 0;
876 mPaint.setFilterBitmap(true); // draw with higher quality since we render with transforms
877 if (drawPath) {
878 for (int i = 0; i < count - 1; i++) {
879 Cell cell = pattern.get(i);
880 Cell next = pattern.get(i + 1);
881
882 // only draw the part of the pattern stored in
883 // the lookup table (this is only different in the case
884 // of animation).
885 if (!drawLookup[next.row][next.column]) {
886 break;
887 }
888
889 float leftX = paddingLeft + cell.column * squareWidth;
890 float topY = paddingTop + cell.row * squareHeight;
891
892 drawArrow(canvas, leftX, topY, cell, next);
893 }
894 }
895
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800896 if (drawPath) {
897 boolean anyCircles = false;
898 for (int i = 0; i < count; i++) {
899 Cell cell = pattern.get(i);
900
901 // only draw the part of the pattern stored in
902 // the lookup table (this is only different in the case
903 // of animation).
904 if (!drawLookup[cell.row][cell.column]) {
905 break;
906 }
907 anyCircles = true;
908
909 float centerX = getCenterXForColumn(cell.column);
910 float centerY = getCenterYForRow(cell.row);
911 if (i == 0) {
912 currentPath.moveTo(centerX, centerY);
913 } else {
914 currentPath.lineTo(centerX, centerY);
915 }
916 }
917
918 // add last in progress section
919 if ((mPatternInProgress || mPatternDisplayMode == DisplayMode.Animate)
920 && anyCircles) {
921 currentPath.lineTo(mInProgressX, mInProgressY);
922 }
923 canvas.drawPath(currentPath, mPathPaint);
924 }
925
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800926 mPaint.setFilterBitmap(oldFlag); // restore default flag
927 }
928
929 private void drawArrow(Canvas canvas, float leftX, float topY, Cell start, Cell end) {
930 boolean green = mPatternDisplayMode != DisplayMode.Wrong;
931
932 final int endRow = end.row;
933 final int startRow = start.row;
934 final int endColumn = end.column;
935 final int startColumn = start.column;
936
937 // offsets for centering the bitmap in the cell
938 final int offsetX = ((int) mSquareWidth - mBitmapWidth) / 2;
939 final int offsetY = ((int) mSquareHeight - mBitmapHeight) / 2;
940
941 // compute transform to place arrow bitmaps at correct angle inside circle.
942 // This assumes that the arrow image is drawn at 12:00 with it's top edge
943 // coincident with the circle bitmap's top edge.
944 Bitmap arrow = green ? mBitmapArrowGreenUp : mBitmapArrowRedUp;
Jim Miller0d244192011-06-16 17:31:21 -0700945 final int cellWidth = mBitmapWidth;
946 final int cellHeight = mBitmapHeight;
Jim Millerbf1259b2010-03-31 18:15:27 -0700947
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800948 // the up arrow bitmap is at 12:00, so find the rotation from x axis and add 90 degrees.
949 final float theta = (float) Math.atan2(
950 (double) (endRow - startRow), (double) (endColumn - startColumn));
Jim Millerbf1259b2010-03-31 18:15:27 -0700951 final float angle = (float) Math.toDegrees(theta) + 90.0f;
952
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800953 // compose matrix
Jim Miller0d244192011-06-16 17:31:21 -0700954 float sx = Math.min(mSquareWidth / mBitmapWidth, 1.0f);
955 float sy = Math.min(mSquareHeight / mBitmapHeight, 1.0f);
Romain Guy5b3b3522010-10-27 18:57:51 -0700956 mArrowMatrix.setTranslate(leftX + offsetX, topY + offsetY); // transform to cell position
Jim Miller0d244192011-06-16 17:31:21 -0700957 mArrowMatrix.preTranslate(mBitmapWidth/2, mBitmapHeight/2);
958 mArrowMatrix.preScale(sx, sy);
959 mArrowMatrix.preTranslate(-mBitmapWidth/2, -mBitmapHeight/2);
Romain Guy5b3b3522010-10-27 18:57:51 -0700960 mArrowMatrix.preRotate(angle, cellWidth / 2.0f, cellHeight / 2.0f); // rotate about cell center
961 mArrowMatrix.preTranslate((cellWidth - arrow.getWidth()) / 2.0f, 0.0f); // translate to 12:00 pos
962 canvas.drawBitmap(arrow, mArrowMatrix, mPaint);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800963 }
964
965 /**
966 * @param canvas
967 * @param leftX
968 * @param topY
969 * @param partOfPattern Whether this circle is part of the pattern.
970 */
971 private void drawCircle(Canvas canvas, int leftX, int topY, boolean partOfPattern) {
972 Bitmap outerCircle;
973 Bitmap innerCircle;
974
Adrian Roose2d71e42014-01-08 15:32:10 -0800975 if (!partOfPattern || mInStealthMode) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800976 // unselected circle
977 outerCircle = mBitmapCircleDefault;
978 innerCircle = mBitmapBtnDefault;
979 } else if (mPatternInProgress) {
980 // user is in middle of drawing a pattern
981 outerCircle = mBitmapCircleGreen;
982 innerCircle = mBitmapBtnTouched;
983 } else if (mPatternDisplayMode == DisplayMode.Wrong) {
984 // the pattern is wrong
985 outerCircle = mBitmapCircleRed;
986 innerCircle = mBitmapBtnDefault;
987 } else if (mPatternDisplayMode == DisplayMode.Correct ||
988 mPatternDisplayMode == DisplayMode.Animate) {
989 // the pattern is correct
990 outerCircle = mBitmapCircleGreen;
991 innerCircle = mBitmapBtnDefault;
992 } else {
993 throw new IllegalStateException("unknown display mode " + mPatternDisplayMode);
994 }
995
996 final int width = mBitmapWidth;
997 final int height = mBitmapHeight;
998
999 final float squareWidth = mSquareWidth;
1000 final float squareHeight = mSquareHeight;
1001
1002 int offsetX = (int) ((squareWidth - width) / 2f);
1003 int offsetY = (int) ((squareHeight - height) / 2f);
1004
Jim Miller0d244192011-06-16 17:31:21 -07001005 // Allow circles to shrink if the view is too small to hold them.
1006 float sx = Math.min(mSquareWidth / mBitmapWidth, 1.0f);
1007 float sy = Math.min(mSquareHeight / mBitmapHeight, 1.0f);
1008
1009 mCircleMatrix.setTranslate(leftX + offsetX, topY + offsetY);
1010 mCircleMatrix.preTranslate(mBitmapWidth/2, mBitmapHeight/2);
1011 mCircleMatrix.preScale(sx, sy);
1012 mCircleMatrix.preTranslate(-mBitmapWidth/2, -mBitmapHeight/2);
1013
1014 canvas.drawBitmap(outerCircle, mCircleMatrix, mPaint);
1015 canvas.drawBitmap(innerCircle, mCircleMatrix, mPaint);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001016 }
1017
1018 @Override
1019 protected Parcelable onSaveInstanceState() {
1020 Parcelable superState = super.onSaveInstanceState();
1021 return new SavedState(superState,
1022 LockPatternUtils.patternToString(mPattern),
1023 mPatternDisplayMode.ordinal(),
Jim Milleraef555b2011-10-12 16:41:30 -07001024 mInputEnabled, mInStealthMode, mEnableHapticFeedback);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001025 }
1026
1027 @Override
1028 protected void onRestoreInstanceState(Parcelable state) {
1029 final SavedState ss = (SavedState) state;
1030 super.onRestoreInstanceState(ss.getSuperState());
1031 setPattern(
1032 DisplayMode.Correct,
1033 LockPatternUtils.stringToPattern(ss.getSerializedPattern()));
1034 mPatternDisplayMode = DisplayMode.values()[ss.getDisplayMode()];
1035 mInputEnabled = ss.isInputEnabled();
1036 mInStealthMode = ss.isInStealthMode();
Jim Milleraef555b2011-10-12 16:41:30 -07001037 mEnableHapticFeedback = ss.isTactileFeedbackEnabled();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001038 }
1039
1040 /**
1041 * The parecelable for saving and restoring a lock pattern view.
1042 */
1043 private static class SavedState extends BaseSavedState {
1044
1045 private final String mSerializedPattern;
1046 private final int mDisplayMode;
1047 private final boolean mInputEnabled;
1048 private final boolean mInStealthMode;
1049 private final boolean mTactileFeedbackEnabled;
1050
1051 /**
1052 * Constructor called from {@link LockPatternView#onSaveInstanceState()}
1053 */
1054 private SavedState(Parcelable superState, String serializedPattern, int displayMode,
1055 boolean inputEnabled, boolean inStealthMode, boolean tactileFeedbackEnabled) {
1056 super(superState);
1057 mSerializedPattern = serializedPattern;
1058 mDisplayMode = displayMode;
1059 mInputEnabled = inputEnabled;
1060 mInStealthMode = inStealthMode;
1061 mTactileFeedbackEnabled = tactileFeedbackEnabled;
1062 }
1063
1064 /**
1065 * Constructor called from {@link #CREATOR}
1066 */
1067 private SavedState(Parcel in) {
1068 super(in);
1069 mSerializedPattern = in.readString();
1070 mDisplayMode = in.readInt();
1071 mInputEnabled = (Boolean) in.readValue(null);
1072 mInStealthMode = (Boolean) in.readValue(null);
1073 mTactileFeedbackEnabled = (Boolean) in.readValue(null);
1074 }
Jim Millerbf1259b2010-03-31 18:15:27 -07001075
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001076 public String getSerializedPattern() {
1077 return mSerializedPattern;
1078 }
1079
1080 public int getDisplayMode() {
1081 return mDisplayMode;
1082 }
1083
1084 public boolean isInputEnabled() {
1085 return mInputEnabled;
1086 }
1087
1088 public boolean isInStealthMode() {
1089 return mInStealthMode;
1090 }
1091
1092 public boolean isTactileFeedbackEnabled(){
1093 return mTactileFeedbackEnabled;
1094 }
1095
1096 @Override
1097 public void writeToParcel(Parcel dest, int flags) {
1098 super.writeToParcel(dest, flags);
1099 dest.writeString(mSerializedPattern);
1100 dest.writeInt(mDisplayMode);
1101 dest.writeValue(mInputEnabled);
1102 dest.writeValue(mInStealthMode);
1103 dest.writeValue(mTactileFeedbackEnabled);
1104 }
1105
1106 public static final Parcelable.Creator<SavedState> CREATOR =
1107 new Creator<SavedState>() {
1108 public SavedState createFromParcel(Parcel in) {
1109 return new SavedState(in);
1110 }
1111
1112 public SavedState[] newArray(int size) {
1113 return new SavedState[size];
1114 }
1115 };
1116 }
1117}