blob: b066d703360a59e05544dbad33466fcee8bd42d3 [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
871 // draw the path of the pattern (unless the user is in progress, and
872 // we are in stealth mode)
873 final boolean drawPath = (!mInStealthMode || mPatternDisplayMode == DisplayMode.Wrong);
Jim Miller08a975e2011-06-22 14:07:44 -0700874
875 // draw the arrows associated with the path (unless the user is in progress, and
876 // we are in stealth mode)
877 boolean oldFlag = (mPaint.getFlags() & Paint.FILTER_BITMAP_FLAG) != 0;
878 mPaint.setFilterBitmap(true); // draw with higher quality since we render with transforms
879 if (drawPath) {
880 for (int i = 0; i < count - 1; i++) {
881 Cell cell = pattern.get(i);
882 Cell next = pattern.get(i + 1);
883
884 // only draw the part of the pattern stored in
885 // the lookup table (this is only different in the case
886 // of animation).
887 if (!drawLookup[next.row][next.column]) {
888 break;
889 }
890
891 float leftX = paddingLeft + cell.column * squareWidth;
892 float topY = paddingTop + cell.row * squareHeight;
893
894 drawArrow(canvas, leftX, topY, cell, next);
895 }
896 }
897
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800898 if (drawPath) {
899 boolean anyCircles = false;
900 for (int i = 0; i < count; i++) {
901 Cell cell = pattern.get(i);
902
903 // only draw the part of the pattern stored in
904 // the lookup table (this is only different in the case
905 // of animation).
906 if (!drawLookup[cell.row][cell.column]) {
907 break;
908 }
909 anyCircles = true;
910
911 float centerX = getCenterXForColumn(cell.column);
912 float centerY = getCenterYForRow(cell.row);
913 if (i == 0) {
914 currentPath.moveTo(centerX, centerY);
915 } else {
916 currentPath.lineTo(centerX, centerY);
917 }
918 }
919
920 // add last in progress section
921 if ((mPatternInProgress || mPatternDisplayMode == DisplayMode.Animate)
922 && anyCircles) {
923 currentPath.lineTo(mInProgressX, mInProgressY);
924 }
925 canvas.drawPath(currentPath, mPathPaint);
926 }
927
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800928 mPaint.setFilterBitmap(oldFlag); // restore default flag
929 }
930
931 private void drawArrow(Canvas canvas, float leftX, float topY, Cell start, Cell end) {
932 boolean green = mPatternDisplayMode != DisplayMode.Wrong;
933
934 final int endRow = end.row;
935 final int startRow = start.row;
936 final int endColumn = end.column;
937 final int startColumn = start.column;
938
939 // offsets for centering the bitmap in the cell
940 final int offsetX = ((int) mSquareWidth - mBitmapWidth) / 2;
941 final int offsetY = ((int) mSquareHeight - mBitmapHeight) / 2;
942
943 // compute transform to place arrow bitmaps at correct angle inside circle.
944 // This assumes that the arrow image is drawn at 12:00 with it's top edge
945 // coincident with the circle bitmap's top edge.
946 Bitmap arrow = green ? mBitmapArrowGreenUp : mBitmapArrowRedUp;
Jim Miller0d244192011-06-16 17:31:21 -0700947 final int cellWidth = mBitmapWidth;
948 final int cellHeight = mBitmapHeight;
Jim Millerbf1259b2010-03-31 18:15:27 -0700949
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800950 // the up arrow bitmap is at 12:00, so find the rotation from x axis and add 90 degrees.
951 final float theta = (float) Math.atan2(
952 (double) (endRow - startRow), (double) (endColumn - startColumn));
Jim Millerbf1259b2010-03-31 18:15:27 -0700953 final float angle = (float) Math.toDegrees(theta) + 90.0f;
954
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800955 // compose matrix
Jim Miller0d244192011-06-16 17:31:21 -0700956 float sx = Math.min(mSquareWidth / mBitmapWidth, 1.0f);
957 float sy = Math.min(mSquareHeight / mBitmapHeight, 1.0f);
Romain Guy5b3b3522010-10-27 18:57:51 -0700958 mArrowMatrix.setTranslate(leftX + offsetX, topY + offsetY); // transform to cell position
Jim Miller0d244192011-06-16 17:31:21 -0700959 mArrowMatrix.preTranslate(mBitmapWidth/2, mBitmapHeight/2);
960 mArrowMatrix.preScale(sx, sy);
961 mArrowMatrix.preTranslate(-mBitmapWidth/2, -mBitmapHeight/2);
Romain Guy5b3b3522010-10-27 18:57:51 -0700962 mArrowMatrix.preRotate(angle, cellWidth / 2.0f, cellHeight / 2.0f); // rotate about cell center
963 mArrowMatrix.preTranslate((cellWidth - arrow.getWidth()) / 2.0f, 0.0f); // translate to 12:00 pos
964 canvas.drawBitmap(arrow, mArrowMatrix, mPaint);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800965 }
966
967 /**
968 * @param canvas
969 * @param leftX
970 * @param topY
971 * @param partOfPattern Whether this circle is part of the pattern.
972 */
973 private void drawCircle(Canvas canvas, int leftX, int topY, boolean partOfPattern) {
974 Bitmap outerCircle;
975 Bitmap innerCircle;
976
977 if (!partOfPattern || (mInStealthMode && mPatternDisplayMode != DisplayMode.Wrong)) {
978 // unselected circle
979 outerCircle = mBitmapCircleDefault;
980 innerCircle = mBitmapBtnDefault;
981 } else if (mPatternInProgress) {
982 // user is in middle of drawing a pattern
983 outerCircle = mBitmapCircleGreen;
984 innerCircle = mBitmapBtnTouched;
985 } else if (mPatternDisplayMode == DisplayMode.Wrong) {
986 // the pattern is wrong
987 outerCircle = mBitmapCircleRed;
988 innerCircle = mBitmapBtnDefault;
989 } else if (mPatternDisplayMode == DisplayMode.Correct ||
990 mPatternDisplayMode == DisplayMode.Animate) {
991 // the pattern is correct
992 outerCircle = mBitmapCircleGreen;
993 innerCircle = mBitmapBtnDefault;
994 } else {
995 throw new IllegalStateException("unknown display mode " + mPatternDisplayMode);
996 }
997
998 final int width = mBitmapWidth;
999 final int height = mBitmapHeight;
1000
1001 final float squareWidth = mSquareWidth;
1002 final float squareHeight = mSquareHeight;
1003
1004 int offsetX = (int) ((squareWidth - width) / 2f);
1005 int offsetY = (int) ((squareHeight - height) / 2f);
1006
Jim Miller0d244192011-06-16 17:31:21 -07001007 // Allow circles to shrink if the view is too small to hold them.
1008 float sx = Math.min(mSquareWidth / mBitmapWidth, 1.0f);
1009 float sy = Math.min(mSquareHeight / mBitmapHeight, 1.0f);
1010
1011 mCircleMatrix.setTranslate(leftX + offsetX, topY + offsetY);
1012 mCircleMatrix.preTranslate(mBitmapWidth/2, mBitmapHeight/2);
1013 mCircleMatrix.preScale(sx, sy);
1014 mCircleMatrix.preTranslate(-mBitmapWidth/2, -mBitmapHeight/2);
1015
1016 canvas.drawBitmap(outerCircle, mCircleMatrix, mPaint);
1017 canvas.drawBitmap(innerCircle, mCircleMatrix, mPaint);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001018 }
1019
1020 @Override
1021 protected Parcelable onSaveInstanceState() {
1022 Parcelable superState = super.onSaveInstanceState();
1023 return new SavedState(superState,
1024 LockPatternUtils.patternToString(mPattern),
1025 mPatternDisplayMode.ordinal(),
Jim Milleraef555b2011-10-12 16:41:30 -07001026 mInputEnabled, mInStealthMode, mEnableHapticFeedback);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001027 }
1028
1029 @Override
1030 protected void onRestoreInstanceState(Parcelable state) {
1031 final SavedState ss = (SavedState) state;
1032 super.onRestoreInstanceState(ss.getSuperState());
1033 setPattern(
1034 DisplayMode.Correct,
1035 LockPatternUtils.stringToPattern(ss.getSerializedPattern()));
1036 mPatternDisplayMode = DisplayMode.values()[ss.getDisplayMode()];
1037 mInputEnabled = ss.isInputEnabled();
1038 mInStealthMode = ss.isInStealthMode();
Jim Milleraef555b2011-10-12 16:41:30 -07001039 mEnableHapticFeedback = ss.isTactileFeedbackEnabled();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001040 }
1041
1042 /**
1043 * The parecelable for saving and restoring a lock pattern view.
1044 */
1045 private static class SavedState extends BaseSavedState {
1046
1047 private final String mSerializedPattern;
1048 private final int mDisplayMode;
1049 private final boolean mInputEnabled;
1050 private final boolean mInStealthMode;
1051 private final boolean mTactileFeedbackEnabled;
1052
1053 /**
1054 * Constructor called from {@link LockPatternView#onSaveInstanceState()}
1055 */
1056 private SavedState(Parcelable superState, String serializedPattern, int displayMode,
1057 boolean inputEnabled, boolean inStealthMode, boolean tactileFeedbackEnabled) {
1058 super(superState);
1059 mSerializedPattern = serializedPattern;
1060 mDisplayMode = displayMode;
1061 mInputEnabled = inputEnabled;
1062 mInStealthMode = inStealthMode;
1063 mTactileFeedbackEnabled = tactileFeedbackEnabled;
1064 }
1065
1066 /**
1067 * Constructor called from {@link #CREATOR}
1068 */
1069 private SavedState(Parcel in) {
1070 super(in);
1071 mSerializedPattern = in.readString();
1072 mDisplayMode = in.readInt();
1073 mInputEnabled = (Boolean) in.readValue(null);
1074 mInStealthMode = (Boolean) in.readValue(null);
1075 mTactileFeedbackEnabled = (Boolean) in.readValue(null);
1076 }
Jim Millerbf1259b2010-03-31 18:15:27 -07001077
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001078 public String getSerializedPattern() {
1079 return mSerializedPattern;
1080 }
1081
1082 public int getDisplayMode() {
1083 return mDisplayMode;
1084 }
1085
1086 public boolean isInputEnabled() {
1087 return mInputEnabled;
1088 }
1089
1090 public boolean isInStealthMode() {
1091 return mInStealthMode;
1092 }
1093
1094 public boolean isTactileFeedbackEnabled(){
1095 return mTactileFeedbackEnabled;
1096 }
1097
1098 @Override
1099 public void writeToParcel(Parcel dest, int flags) {
1100 super.writeToParcel(dest, flags);
1101 dest.writeString(mSerializedPattern);
1102 dest.writeInt(mDisplayMode);
1103 dest.writeValue(mInputEnabled);
1104 dest.writeValue(mInStealthMode);
1105 dest.writeValue(mTactileFeedbackEnabled);
1106 }
1107
1108 public static final Parcelable.Creator<SavedState> CREATOR =
1109 new Creator<SavedState>() {
1110 public SavedState createFromParcel(Parcel in) {
1111 return new SavedState(in);
1112 }
1113
1114 public SavedState[] newArray(int size) {
1115 return new SavedState[size];
1116 }
1117 };
1118 }
1119}