diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 18:28:45 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 18:28:45 -0800 |
commit | d83a98f4ce9cfa908f5c54bbd70f03eec07e7553 (patch) | |
tree | 4b825dc642cb6eb9a060e54bf8d69288fbee4904 /core/java/com/android/internal/widget | |
parent | 076357b8567458d4b6dfdcf839ef751634cd2bfb (diff) | |
download | frameworks_base-d83a98f4ce9cfa908f5c54bbd70f03eec07e7553.zip frameworks_base-d83a98f4ce9cfa908f5c54bbd70f03eec07e7553.tar.gz frameworks_base-d83a98f4ce9cfa908f5c54bbd70f03eec07e7553.tar.bz2 |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'core/java/com/android/internal/widget')
9 files changed, 0 insertions, 2773 deletions
diff --git a/core/java/com/android/internal/widget/DialogTitle.java b/core/java/com/android/internal/widget/DialogTitle.java deleted file mode 100644 index 2eef0b6..0000000 --- a/core/java/com/android/internal/widget/DialogTitle.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2008 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.widget; - -import android.content.Context; -import android.content.res.TypedArray; -import android.text.Layout; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.widget.TextView; - -/** - * Used by dialogs to change the font size and number of lines to try to fit - * the text to the available space. - */ -public class DialogTitle extends TextView { - - public DialogTitle(Context context, AttributeSet attrs, - int defStyle) { - super(context, attrs, defStyle); - } - - public DialogTitle(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public DialogTitle(Context context) { - super(context); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - final Layout layout = getLayout(); - if (layout != null) { - final int lineCount = layout.getLineCount(); - if (lineCount > 0) { - final int ellipsisCount = layout.getEllipsisCount(lineCount - 1); - if (ellipsisCount > 0) { - setSingleLine(false); - - TypedArray a = mContext.obtainStyledAttributes( - android.R.style.TextAppearance_Medium, - android.R.styleable.TextAppearance); - final int textSize = a.getDimensionPixelSize( - android.R.styleable.TextAppearance_textSize, 20); - - setTextSize(TypedValue.COMPLEX_UNIT_SP, textSize); - setMaxLines(2); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - } - } - } - -}
\ No newline at end of file diff --git a/core/java/com/android/internal/widget/EditableInputConnection.java b/core/java/com/android/internal/widget/EditableInputConnection.java deleted file mode 100644 index 44cf0ed..0000000 --- a/core/java/com/android/internal/widget/EditableInputConnection.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2007-2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.android.internal.widget; - -import android.os.Bundle; -import android.text.Editable; -import android.text.method.KeyListener; -import android.util.Log; -import android.view.inputmethod.BaseInputConnection; -import android.view.inputmethod.CompletionInfo; -import android.view.inputmethod.ExtractedText; -import android.view.inputmethod.ExtractedTextRequest; -import android.widget.TextView; - -public class EditableInputConnection extends BaseInputConnection { - private static final boolean DEBUG = false; - private static final String TAG = "EditableInputConnection"; - - private final TextView mTextView; - - public EditableInputConnection(TextView textview) { - super(textview, false); - mTextView = textview; - } - - public Editable getEditable() { - TextView tv = mTextView; - if (tv != null) { - return tv.getEditableText(); - } - return null; - } - - public boolean beginBatchEdit() { - mTextView.beginBatchEdit(); - return true; - } - - public boolean endBatchEdit() { - mTextView.endBatchEdit(); - return true; - } - - public boolean clearMetaKeyStates(int states) { - final Editable content = getEditable(); - if (content == null) return false; - KeyListener kl = mTextView.getKeyListener(); - if (kl != null) kl.clearMetaKeyState(mTextView, content, states); - return true; - } - - public boolean commitCompletion(CompletionInfo text) { - if (DEBUG) Log.v(TAG, "commitCompletion " + text); - mTextView.beginBatchEdit(); - mTextView.onCommitCompletion(text); - mTextView.endBatchEdit(); - return true; - } - - public boolean performContextMenuAction(int id) { - if (DEBUG) Log.v(TAG, "performContextMenuAction " + id); - mTextView.beginBatchEdit(); - mTextView.onTextContextMenuItem(id); - mTextView.endBatchEdit(); - return true; - } - - public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) { - if (mTextView != null) { - ExtractedText et = new ExtractedText(); - if (mTextView.extractText(request, et)) { - if ((flags&GET_EXTRACTED_TEXT_MONITOR) != 0) { - mTextView.setExtracting(request); - } - return et; - } - } - return null; - } - - public boolean performPrivateCommand(String action, Bundle data) { - mTextView.onPrivateIMECommand(action, data); - return true; - } - - @Override - public boolean commitText(CharSequence text, int newCursorPosition) { - if (mTextView == null) { - return super.commitText(text, newCursorPosition); - } - - CharSequence errorBefore = mTextView.getError(); - boolean success = super.commitText(text, newCursorPosition); - CharSequence errorAfter = mTextView.getError(); - - if (errorAfter != null && errorBefore == errorAfter) { - mTextView.setError(null, null); - } - - return success; - } -} diff --git a/core/java/com/android/internal/widget/LinearLayoutWithDefaultTouchRecepient.java b/core/java/com/android/internal/widget/LinearLayoutWithDefaultTouchRecepient.java deleted file mode 100644 index b2001cb..0000000 --- a/core/java/com/android/internal/widget/LinearLayoutWithDefaultTouchRecepient.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.widget; - -import android.content.Context; -import android.graphics.Rect; -import android.util.AttributeSet; -import android.view.View; -import android.view.MotionEvent; -import android.widget.LinearLayout; - - -/** - * Like a normal linear layout, but supports dispatching all otherwise unhandled - * touch events to a particular descendant. This is for the unlock screen, so - * that a wider range of touch events than just the lock pattern widget can kick - * off a lock pattern if the finger is eventually dragged into the bounds of the - * lock pattern view. - */ -public class LinearLayoutWithDefaultTouchRecepient extends LinearLayout { - - private final Rect mTempRect = new Rect(); - private View mDefaultTouchRecepient; - - public LinearLayoutWithDefaultTouchRecepient(Context context) { - super(context); - } - - public LinearLayoutWithDefaultTouchRecepient(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public void setDefaultTouchRecepient(View defaultTouchRecepient) { - mDefaultTouchRecepient = defaultTouchRecepient; - } - - @Override - public boolean dispatchTouchEvent(MotionEvent ev) { - if (mDefaultTouchRecepient == null) { - return super.dispatchTouchEvent(ev); - } - - if (super.dispatchTouchEvent(ev)) { - return true; - } - mTempRect.set(0, 0, 0, 0); - offsetRectIntoDescendantCoords(mDefaultTouchRecepient, mTempRect); - ev.setLocation(ev.getX() + mTempRect.left, ev.getY() + mTempRect.top); - return mDefaultTouchRecepient.dispatchTouchEvent(ev); - } - -} diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java deleted file mode 100644 index c8b3ad4..0000000 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ /dev/null @@ -1,359 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.widget; - -import android.content.ContentResolver; -import android.os.SystemClock; -import android.provider.Settings; -import android.security.MessageDigest; -import android.text.TextUtils; -import android.util.Log; - -import com.google.android.collect.Lists; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; -import java.util.List; - -/** - * Utilities for the lock patten and its settings. - */ -public class LockPatternUtils { - - private static final String TAG = "LockPatternUtils"; - - private static final String LOCK_PATTERN_FILE = "/system/gesture.key"; - - /** - * The maximum number of incorrect attempts before the user is prevented - * from trying again for {@link #FAILED_ATTEMPT_TIMEOUT_MS}. - */ - public static final int FAILED_ATTEMPTS_BEFORE_TIMEOUT = 5; - - /** - * The number of incorrect attempts before which we fall back on an alternative - * method of verifying the user, and resetting their lock pattern. - */ - public static final int FAILED_ATTEMPTS_BEFORE_RESET = 20; - - /** - * How long the user is prevented from trying again after entering the - * wrong pattern too many times. - */ - public static final long FAILED_ATTEMPT_TIMEOUT_MS = 30000L; - - /** - * The interval of the countdown for showing progress of the lockout. - */ - public static final long FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS = 1000L; - - /** - * The minimum number of dots in a valid pattern. - */ - public static final int MIN_LOCK_PATTERN_SIZE = 4; - - /** - * The minimum number of dots the user must include in a wrong pattern - * attempt for it to be counted against the counts that affect - * {@link #FAILED_ATTEMPTS_BEFORE_TIMEOUT} and {@link #FAILED_ATTEMPTS_BEFORE_RESET} - */ - public static final int MIN_PATTERN_REGISTER_FAIL = 3; - - private final static String LOCKOUT_PERMANENT_KEY = "lockscreen.lockedoutpermanently"; - private final static String LOCKOUT_ATTEMPT_DEADLINE = "lockscreen.lockoutattemptdeadline"; - - private final ContentResolver mContentResolver; - - private static String sLockPatternFilename; - - /** - * @param contentResolver Used to look up and save settings. - */ - public LockPatternUtils(ContentResolver contentResolver) { - mContentResolver = contentResolver; - // Initialize the location of gesture lock file - if (sLockPatternFilename == null) { - sLockPatternFilename = android.os.Environment.getDataDirectory() - .getAbsolutePath() + LOCK_PATTERN_FILE; - } - } - - /** - * Check to see if a pattern matches the saved pattern. If no pattern exists, - * always returns true. - * @param pattern The pattern to check. - * @return Whether the pattern matchees the stored one. - */ - public boolean checkPattern(List<LockPatternView.Cell> pattern) { - try { - // Read all the bytes from the file - RandomAccessFile raf = new RandomAccessFile(sLockPatternFilename, "r"); - final byte[] stored = new byte[(int) raf.length()]; - int got = raf.read(stored, 0, stored.length); - raf.close(); - if (got <= 0) { - return true; - } - // Compare the hash from the file with the entered pattern's hash - return Arrays.equals(stored, LockPatternUtils.patternToHash(pattern)); - } catch (FileNotFoundException fnfe) { - return true; - } catch (IOException ioe) { - return true; - } - } - - /** - * Check to see if the user has stored a lock pattern. - * @return Whether a saved pattern exists. - */ - public boolean savedPatternExists() { - try { - // Check if we can read a byte from the file - RandomAccessFile raf = new RandomAccessFile(sLockPatternFilename, "r"); - byte first = raf.readByte(); - raf.close(); - return true; - } catch (FileNotFoundException fnfe) { - return false; - } catch (IOException ioe) { - return false; - } - } - - /** - * Save a lock pattern. - * @param pattern The new pattern to save. - */ - public void saveLockPattern(List<LockPatternView.Cell> pattern) { - // Compute the hash - final byte[] hash = LockPatternUtils.patternToHash(pattern); - try { - // Write the hash to file - RandomAccessFile raf = new RandomAccessFile(sLockPatternFilename, "rw"); - // Truncate the file if pattern is null, to clear the lock - if (pattern == null) { - raf.setLength(0); - } else { - raf.write(hash, 0, hash.length); - } - raf.close(); - } catch (FileNotFoundException fnfe) { - // Cant do much, unless we want to fail over to using the settings provider - Log.e(TAG, "Unable to save lock pattern to " + sLockPatternFilename); - } catch (IOException ioe) { - // Cant do much - Log.e(TAG, "Unable to save lock pattern to " + sLockPatternFilename); - } - } - - /** - * Deserialize a pattern. - * @param string The pattern serialized with {@link #patternToString} - * @return The pattern. - */ - public static List<LockPatternView.Cell> stringToPattern(String string) { - List<LockPatternView.Cell> result = Lists.newArrayList(); - - final byte[] bytes = string.getBytes(); - for (int i = 0; i < bytes.length; i++) { - byte b = bytes[i]; - result.add(LockPatternView.Cell.of(b / 3, b % 3)); - } - return result; - } - - /** - * Serialize a pattern. - * @param pattern The pattern. - * @return The pattern in string form. - */ - public static String patternToString(List<LockPatternView.Cell> pattern) { - if (pattern == null) { - return ""; - } - final int patternSize = pattern.size(); - - byte[] res = new byte[patternSize]; - for (int i = 0; i < patternSize; i++) { - LockPatternView.Cell cell = pattern.get(i); - res[i] = (byte) (cell.getRow() * 3 + cell.getColumn()); - } - return new String(res); - } - - /* - * Generate an SHA-1 hash for the pattern. Not the most secure, but it is - * at least a second level of protection. First level is that the file - * is in a location only readable by the system process. - * @param pattern the gesture pattern. - * @return the hash of the pattern in a byte array. - */ - static byte[] patternToHash(List<LockPatternView.Cell> pattern) { - if (pattern == null) { - return null; - } - - final int patternSize = pattern.size(); - byte[] res = new byte[patternSize]; - for (int i = 0; i < patternSize; i++) { - LockPatternView.Cell cell = pattern.get(i); - res[i] = (byte) (cell.getRow() * 3 + cell.getColumn()); - } - try { - MessageDigest md = MessageDigest.getInstance("SHA-1"); - byte[] hash = md.digest(res); - return hash; - } catch (NoSuchAlgorithmException nsa) { - return res; - } - } - - /** - * @return Whether the lock pattern is enabled. - */ - public boolean isLockPatternEnabled() { - return getBoolean(Settings.System.LOCK_PATTERN_ENABLED); - } - - /** - * Set whether the lock pattern is enabled. - */ - public void setLockPatternEnabled(boolean enabled) { - setBoolean(Settings.System.LOCK_PATTERN_ENABLED, enabled); - } - - /** - * @return Whether the visible pattern is enabled. - */ - public boolean isVisiblePatternEnabled() { - return getBoolean(Settings.System.LOCK_PATTERN_VISIBLE); - } - - /** - * Set whether the visible pattern is enabled. - */ - public void setVisiblePatternEnabled(boolean enabled) { - setBoolean(Settings.System.LOCK_PATTERN_VISIBLE, enabled); - } - - /** - * @return Whether tactile feedback for the pattern is enabled. - */ - public boolean isTactileFeedbackEnabled() { - return getBoolean(Settings.System.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED); - } - - /** - * Set whether tactile feedback for the pattern is enabled. - */ - public void setTactileFeedbackEnabled(boolean enabled) { - setBoolean(Settings.System.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED, enabled); - } - - /** - * Set and store the lockout deadline, meaning the user can't attempt his/her unlock - * pattern until the deadline has passed. - * @return the chosen deadline. - */ - public long setLockoutAttemptDeadline() { - final long deadline = SystemClock.elapsedRealtime() + FAILED_ATTEMPT_TIMEOUT_MS; - setLong(LOCKOUT_ATTEMPT_DEADLINE, deadline); - return deadline; - } - - /** - * @return The elapsed time in millis in the future when the user is allowed to - * attempt to enter his/her lock pattern, or 0 if the user is welcome to - * enter a pattern. - */ - public long getLockoutAttemptDeadline() { - final long deadline = getLong(LOCKOUT_ATTEMPT_DEADLINE, 0L); - final long now = SystemClock.elapsedRealtime(); - if (deadline < now || deadline > (now + FAILED_ATTEMPT_TIMEOUT_MS)) { - return 0L; - } - return deadline; - } - - /** - * @return Whether the user is permanently locked out until they verify their - * credentials. Occurs after {@link #FAILED_ATTEMPTS_BEFORE_RESET} failed - * attempts. - */ - public boolean isPermanentlyLocked() { - return getBoolean(LOCKOUT_PERMANENT_KEY); - } - - /** - * Set the state of whether the device is permanently locked, meaning the user - * must authenticate via other means. If false, that means the user has gone - * out of permanent lock, so the existing (forgotten) lock pattern needs to - * be cleared. - * @param locked Whether the user is permanently locked out until they verify their - * credentials. Occurs after {@link #FAILED_ATTEMPTS_BEFORE_RESET} failed - * attempts. - */ - public void setPermanentlyLocked(boolean locked) { - setBoolean(LOCKOUT_PERMANENT_KEY, locked); - - if (!locked) { - setLockPatternEnabled(false); - saveLockPattern(null); - } - } - - /** - * @return A formatted string of the next alarm (for showing on the lock screen), - * or null if there is no next alarm. - */ - public String getNextAlarm() { - String nextAlarm = Settings.System.getString(mContentResolver, - Settings.System.NEXT_ALARM_FORMATTED); - if (nextAlarm == null || TextUtils.isEmpty(nextAlarm)) { - return null; - } - return nextAlarm; - } - - private boolean getBoolean(String systemSettingKey) { - return 1 == - android.provider.Settings.System.getInt( - mContentResolver, - systemSettingKey, 0); - } - - private void setBoolean(String systemSettingKey, boolean enabled) { - android.provider.Settings.System.putInt( - mContentResolver, - systemSettingKey, - enabled ? 1 : 0); - } - - private long getLong(String systemSettingKey, long def) { - return android.provider.Settings.System.getLong(mContentResolver, systemSettingKey, def); - } - - private void setLong(String systemSettingKey, long value) { - android.provider.Settings.System.putLong(mContentResolver, systemSettingKey, value); - } - - -} diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java deleted file mode 100644 index 7f99ac8..0000000 --- a/core/java/com/android/internal/widget/LockPatternView.java +++ /dev/null @@ -1,1024 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.widget; - - -import com.android.internal.R; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Matrix; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.Rect; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.os.Parcel; -import android.os.Parcelable; -import android.os.SystemClock; -import android.os.Debug; -import android.os.Vibrator; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.view.WindowManager; - -import java.util.ArrayList; -import java.util.List; - -/** - * Displays and detects the user's unlock attempt, which is a drag of a finger - * across 9 regions of the screen. - * - * Is also capable of displaying a static pattern in "in progress", "wrong" or - * "correct" states. - */ -public class LockPatternView extends View { - // Vibrator pattern for creating a tactile bump - private static final long[] VIBE_PATTERN = {0, 1, 40, 41}; - - private static final boolean PROFILE_DRAWING = false; - private boolean mDrawingProfilingStarted = false; - - private Paint mPaint = new Paint(); - private Paint mPathPaint = new Paint(); - - // TODO: make this common with PhoneWindow - static final int STATUS_BAR_HEIGHT = 25; - - /** - * How many milliseconds we spend animating each circle of a lock pattern - * if the animating mode is set. The entire animation should take this - * constant * the length of the pattern to complete. - */ - private static final int MILLIS_PER_CIRCLE_ANIMATING = 700; - - private OnPatternListener mOnPatternListener; - private ArrayList<Cell> mPattern = new ArrayList<Cell>(9); - - /** - * Lookup table for the circles of the pattern we are currently drawing. - * This will be the cells of the complete pattern unless we are animating, - * in which case we use this to hold the cells we are drawing for the in - * progress animation. - */ - private boolean[][] mPatternDrawLookup = new boolean[3][3]; - - /** - * the in progress point: - * - during interaction: where the user's finger is - * - during animation: the current tip of the animating line - */ - private float mInProgressX = -1; - private float mInProgressY = -1; - - private long mAnimatingPeriodStart; - - private DisplayMode mPatternDisplayMode = DisplayMode.Correct; - private boolean mInputEnabled = true; - private boolean mInStealthMode = false; - private boolean mTactileFeedbackEnabled = true; - private boolean mPatternInProgress = false; - - private float mDiameterFactor = 0.5f; - private float mHitFactor = 0.6f; - - private float mSquareWidth; - private float mSquareHeight; - - private Bitmap mBitmapBtnDefault; - private Bitmap mBitmapBtnTouched; - private Bitmap mBitmapCircleDefault; - private Bitmap mBitmapCircleGreen; - private Bitmap mBitmapCircleRed; - - private Bitmap mBitmapArrowGreenUp; - private Bitmap mBitmapArrowRedUp; - - private final Path mCurrentPath = new Path(); - private final Rect mInvalidate = new Rect(); - - private int mBitmapWidth; - private int mBitmapHeight; - - - private Vibrator vibe; // Vibrator for creating tactile feedback - - /** - * Represents a cell in the 3 X 3 matrix of the unlock pattern view. - */ - public static class Cell { - int row; - int column; - - // keep # objects limited to 9 - static Cell[][] sCells = new Cell[3][3]; - static { - for (int i = 0; i < 3; i++) { - for (int j = 0; j < 3; j++) { - sCells[i][j] = new Cell(i, j); - } - } - } - - /** - * @param row The row of the cell. - * @param column The column of the cell. - */ - private Cell(int row, int column) { - checkRange(row, column); - this.row = row; - this.column = column; - } - - public int getRow() { - return row; - } - - public int getColumn() { - return column; - } - - /** - * @param row The row of the cell. - * @param column The column of the cell. - */ - public static synchronized Cell of(int row, int column) { - checkRange(row, column); - return sCells[row][column]; - } - - private static void checkRange(int row, int column) { - if (row < 0 || row > 2) { - throw new IllegalArgumentException("row must be in range 0-2"); - } - if (column < 0 || column > 2) { - throw new IllegalArgumentException("column must be in range 0-2"); - } - } - - public String toString() { - return "(row=" + row + ",clmn=" + column + ")"; - } - } - - /** - * How to display the current pattern. - */ - public enum DisplayMode { - - /** - * The pattern drawn is correct (i.e draw it in a friendly color) - */ - Correct, - - /** - * Animate the pattern (for demo, and help). - */ - Animate, - - /** - * The pattern is wrong (i.e draw a foreboding color) - */ - Wrong - } - - /** - * The call back interface for detecting patterns entered by the user. - */ - public static interface OnPatternListener { - - /** - * A new pattern has begun. - */ - void onPatternStart(); - - /** - * The pattern was cleared. - */ - void onPatternCleared(); - - /** - * A pattern was detected from the user. - * @param pattern The pattern. - */ - void onPatternDetected(List<Cell> pattern); - } - - public LockPatternView(Context context) { - this(context, null); - } - - public LockPatternView(Context context, AttributeSet attrs) { - super(context, attrs); - vibe = new Vibrator(); - - setClickable(true); - - mPathPaint.setAntiAlias(true); - mPathPaint.setDither(true); - mPathPaint.setColor(Color.WHITE); // TODO this should be from the style - mPathPaint.setAlpha(128); - mPathPaint.setStyle(Paint.Style.STROKE); - mPathPaint.setStrokeJoin(Paint.Join.ROUND); - mPathPaint.setStrokeCap(Paint.Cap.ROUND); - - // lot's of bitmaps! - mBitmapBtnDefault = getBitmapFor(R.drawable.btn_code_lock_default); - mBitmapBtnTouched = getBitmapFor(R.drawable.btn_code_lock_touched); - mBitmapCircleDefault = getBitmapFor(R.drawable.indicator_code_lock_point_area_default); - mBitmapCircleGreen = getBitmapFor(R.drawable.indicator_code_lock_point_area_green); - mBitmapCircleRed = getBitmapFor(R.drawable.indicator_code_lock_point_area_red); - - mBitmapArrowGreenUp = getBitmapFor(R.drawable.indicator_code_lock_drag_direction_green_up); - mBitmapArrowRedUp = getBitmapFor(R.drawable.indicator_code_lock_drag_direction_red_up); - - // we assume all bitmaps have the same size - mBitmapWidth = mBitmapBtnDefault.getWidth(); - mBitmapHeight = mBitmapBtnDefault.getHeight(); - } - - private Bitmap getBitmapFor(int resId) { - return BitmapFactory.decodeResource(getContext().getResources(), resId); - } - - /** - * @return Whether the view is in stealth mode. - */ - public boolean isInStealthMode() { - return mInStealthMode; - } - - /** - * @return Whether the view has tactile feedback enabled. - */ - public boolean isTactileFeedbackEnabled() { - return mTactileFeedbackEnabled; - } - - /** - * Set whether the view is in stealth mode. If true, there will be no - * visible feedback as the user enters the pattern. - * - * @param inStealthMode Whether in stealth mode. - */ - public void setInStealthMode(boolean inStealthMode) { - mInStealthMode = inStealthMode; - } - - /** - * Set whether the view will use tactile feedback. If true, there will be - * tactile feedback as the user enters the pattern. - * - * @param tactileFeedbackEnabled Whether tactile feedback is enabled - */ - public void setTactileFeedbackEnabled(boolean tactileFeedbackEnabled) { - mTactileFeedbackEnabled = tactileFeedbackEnabled; - } - - /** - * Set the call back for pattern detection. - * @param onPatternListener The call back. - */ - public void setOnPatternListener( - OnPatternListener onPatternListener) { - mOnPatternListener = onPatternListener; - } - - /** - * Set the pattern explicitely (rather than waiting for the user to input - * a pattern). - * @param displayMode How to display the pattern. - * @param pattern The pattern. - */ - public void setPattern(DisplayMode displayMode, List<Cell> pattern) { - mPattern.clear(); - mPattern.addAll(pattern); - clearPatternDrawLookup(); - for (Cell cell : pattern) { - mPatternDrawLookup[cell.getRow()][cell.getColumn()] = true; - } - - setDisplayMode(displayMode); - } - - /** - * Set the display mode of the current pattern. This can be useful, for - * instance, after detecting a pattern to tell this view whether change the - * in progress result to correct or wrong. - * @param displayMode The display mode. - */ - public void setDisplayMode(DisplayMode displayMode) { - mPatternDisplayMode = displayMode; - if (displayMode == DisplayMode.Animate) { - if (mPattern.size() == 0) { - throw new IllegalStateException("you must have a pattern to " - + "animate if you want to set the display mode to animate"); - } - mAnimatingPeriodStart = SystemClock.elapsedRealtime(); - final Cell first = mPattern.get(0); - mInProgressX = getCenterXForColumn(first.getColumn()); - mInProgressY = getCenterYForRow(first.getRow()); - clearPatternDrawLookup(); - } - invalidate(); - } - - /** - * Clear the pattern. - */ - public void clearPattern() { - resetPattern(); - } - - /** - * Reset all pattern state. - */ - private void resetPattern() { - mPattern.clear(); - clearPatternDrawLookup(); - mPatternDisplayMode = DisplayMode.Correct; - invalidate(); - } - - /** - * Clear the pattern lookup table. - */ - private void clearPatternDrawLookup() { - for (int i = 0; i < 3; i++) { - for (int j = 0; j < 3; j++) { - mPatternDrawLookup[i][j] = false; - } - } - } - - /** - * Disable input (for instance when displaying a message that will - * timeout so user doesn't get view into messy state). - */ - public void disableInput() { - mInputEnabled = false; - } - - /** - * Enable input. - */ - public void enableInput() { - mInputEnabled = true; - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - final int width = w - mPaddingLeft - mPaddingRight; - mSquareWidth = width / 3.0f; - - final int height = h - mPaddingTop - mPaddingBottom; - mSquareHeight = height / 3.0f; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - final WindowManager wm = (WindowManager) getContext() - .getSystemService(Context.WINDOW_SERVICE); - final int width = wm.getDefaultDisplay().getWidth(); - final int height = wm.getDefaultDisplay().getHeight(); - int squareSide = Math.min(width, height); - - // if in landscape... - if (width > height) { - squareSide -= STATUS_BAR_HEIGHT; - } - - setMeasuredDimension(squareSide, squareSide); - } - - /** - * Determines whether the point x, y will add a new point to the current - * pattern (in addition to finding the cell, also makes heuristic choices - * such as filling in gaps based on current pattern). - * @param x The x coordinate. - * @param y The y coordinate. - */ - private Cell detectAndAddHit(float x, float y) { - final Cell cell = checkForNewHit(x, y); - if (cell != null) { - - // check for gaps in existing pattern - Cell fillInGapCell = null; - final ArrayList<Cell> pattern = mPattern; - if (!pattern.isEmpty()) { - final Cell lastCell = pattern.get(pattern.size() - 1); - int dRow = cell.row - lastCell.row; - int dColumn = cell.column - lastCell.column; - - int fillInRow = lastCell.row; - int fillInColumn = lastCell.column; - - if (Math.abs(dRow) == 2 && Math.abs(dColumn) != 1) { - fillInRow = lastCell.row + ((dRow > 0) ? 1 : -1); - } - - if (Math.abs(dColumn) == 2 && Math.abs(dRow) != 1) { - fillInColumn = lastCell.column + ((dColumn > 0) ? 1 : -1); - } - - fillInGapCell = Cell.of(fillInRow, fillInColumn); - } - - if (fillInGapCell != null && - !mPatternDrawLookup[fillInGapCell.row][fillInGapCell.column]) { - addCellToPattern(fillInGapCell); - } - addCellToPattern(cell); - if (mTactileFeedbackEnabled){ - vibe.vibrate(VIBE_PATTERN, -1); // Generate tactile feedback - } - return cell; - } - return null; - } - - private void addCellToPattern(Cell newCell) { - mPatternDrawLookup[newCell.getRow()][newCell.getColumn()] = true; - mPattern.add(newCell); - } - - // helper method to find which cell a point maps to - private Cell checkForNewHit(float x, float y) { - - final int rowHit = getRowHit(y); - if (rowHit < 0) { - return null; - } - final int columnHit = getColumnHit(x); - if (columnHit < 0) { - return null; - } - - if (mPatternDrawLookup[rowHit][columnHit]) { - return null; - } - return Cell.of(rowHit, columnHit); - } - - /** - * Helper method to find the row that y falls into. - * @param y The y coordinate - * @return The row that y falls in, or -1 if it falls in no row. - */ - private int getRowHit(float y) { - - final float squareHeight = mSquareHeight; - float hitSize = squareHeight * mHitFactor; - - float offset = mPaddingTop + (squareHeight - hitSize) / 2f; - for (int i = 0; i < 3; i++) { - - final float hitTop = offset + squareHeight * i; - if (y >= hitTop && y <= hitTop + hitSize) { - return i; - } - } - return -1; - } - - /** - * Helper method to find the column x fallis into. - * @param x The x coordinate. - * @return The column that x falls in, or -1 if it falls in no column. - */ - private int getColumnHit(float x) { - final float squareWidth = mSquareWidth; - float hitSize = squareWidth * mHitFactor; - - float offset = mPaddingLeft + (squareWidth - hitSize) / 2f; - for (int i = 0; i < 3; i++) { - - final float hitLeft = offset + squareWidth * i; - if (x >= hitLeft && x <= hitLeft + hitSize) { - return i; - } - } - return -1; - } - - @Override - public boolean onTouchEvent(MotionEvent motionEvent) { - if (!mInputEnabled || !isEnabled()) { - return false; - } - - final float x = motionEvent.getX(); - final float y = motionEvent.getY(); - Cell hitCell; - switch(motionEvent.getAction()) { - case MotionEvent.ACTION_DOWN: - resetPattern(); - hitCell = detectAndAddHit(x, y); - if (hitCell != null && mOnPatternListener != null) { - mPatternInProgress = true; - mPatternDisplayMode = DisplayMode.Correct; - mOnPatternListener.onPatternStart(); - } else if (mOnPatternListener != null) { - mPatternInProgress = false; - mOnPatternListener.onPatternCleared(); - } - if (hitCell != null) { - final float startX = getCenterXForColumn(hitCell.column); - final float startY = getCenterYForRow(hitCell.row); - - final float widthOffset = mSquareWidth / 2f; - final float heightOffset = mSquareHeight / 2f; - - invalidate((int) (startX - widthOffset), (int) (startY - heightOffset), - (int) (startX + widthOffset), (int) (startY + heightOffset)); - } - mInProgressX = x; - mInProgressY = y; - if (PROFILE_DRAWING) { - if (!mDrawingProfilingStarted) { - Debug.startMethodTracing("LockPatternDrawing"); - mDrawingProfilingStarted = true; - } - } - return true; - case MotionEvent.ACTION_UP: - // report pattern detected - if (!mPattern.isEmpty() && mOnPatternListener != null) { - mPatternInProgress = false; - mOnPatternListener.onPatternDetected(mPattern); - invalidate(); - } - if (PROFILE_DRAWING) { - if (mDrawingProfilingStarted) { - Debug.stopMethodTracing(); - mDrawingProfilingStarted = false; - } - } - return true; - case MotionEvent.ACTION_MOVE: - final int patternSizePreHitDetect = mPattern.size(); - hitCell = detectAndAddHit(x, y); - final int patternSize = mPattern.size(); - if (hitCell != null && (mOnPatternListener != null) && (patternSize == 1)) { - mPatternInProgress = true; - mOnPatternListener.onPatternStart(); - } - // note current x and y for rubber banding of in progress - // patterns - final float dx = Math.abs(x - mInProgressX); - final float dy = Math.abs(y - mInProgressY); - if (dx + dy > mSquareWidth * 0.01f) { - float oldX = mInProgressX; - float oldY = mInProgressY; - - mInProgressX = x; - mInProgressY = y; - - if (mPatternInProgress) { - final ArrayList<Cell> pattern = mPattern; - final float radius = mSquareWidth * mDiameterFactor * 0.5f; - - Cell cell = pattern.get(patternSize - 1); - - float startX = getCenterXForColumn(cell.column); - float startY = getCenterYForRow(cell.row); - - float left; - float top; - float right; - float bottom; - - final Rect invalidateRect = mInvalidate; - - if (startX < x) { - left = startX; - right = x; - } else { - left = x; - right = startX; - } - - if (startY < y) { - top = startY; - bottom = y; - } else { - top = y; - bottom = startY; - } - - // Invalidate between the pattern's last cell and the current location - invalidateRect.set((int) (left - radius), (int) (top - radius), - (int) (right + radius), (int) (bottom + radius)); - - if (startX < oldX) { - left = startX; - right = oldX; - } else { - left = oldX; - right = startX; - } - - if (startY < oldY) { - top = startY; - bottom = oldY; - } else { - top = oldY; - bottom = startY; - } - - // Invalidate between the pattern's last cell and the previous location - invalidateRect.union((int) (left - radius), (int) (top - radius), - (int) (right + radius), (int) (bottom + radius)); - - // Invalidate between the pattern's new cell and the pattern's previous cell - if (hitCell != null) { - startX = getCenterXForColumn(hitCell.column); - startY = getCenterYForRow(hitCell.row); - - if (patternSize >= 2) { - // (re-using hitcell for old cell) - hitCell = pattern.get(patternSize - 1 - (patternSize - patternSizePreHitDetect)); - oldX = getCenterXForColumn(hitCell.column); - oldY = getCenterYForRow(hitCell.row); - - if (startX < oldX) { - left = startX; - right = oldX; - } else { - left = oldX; - right = startX; - } - - if (startY < oldY) { - top = startY; - bottom = oldY; - } else { - top = oldY; - bottom = startY; - } - } else { - left = right = startX; - top = bottom = startY; - } - - final float widthOffset = mSquareWidth / 2f; - final float heightOffset = mSquareHeight / 2f; - - invalidateRect.set((int) (left - widthOffset), - (int) (top - heightOffset), (int) (right + widthOffset), - (int) (bottom + heightOffset)); - } - - invalidate(invalidateRect); - } else { - invalidate(); - } - } - return true; - case MotionEvent.ACTION_CANCEL: - resetPattern(); - if (mOnPatternListener != null) { - mPatternInProgress = false; - mOnPatternListener.onPatternCleared(); - } - if (PROFILE_DRAWING) { - if (mDrawingProfilingStarted) { - Debug.stopMethodTracing(); - mDrawingProfilingStarted = false; - } - } - return true; - } - return false; - } - - private float getCenterXForColumn(int column) { - return mPaddingLeft + column * mSquareWidth + mSquareWidth / 2f; - } - - private float getCenterYForRow(int row) { - return mPaddingTop + row * mSquareHeight + mSquareHeight / 2f; - } - - @Override - protected void onDraw(Canvas canvas) { - final ArrayList<Cell> pattern = mPattern; - final int count = pattern.size(); - final boolean[][] drawLookup = mPatternDrawLookup; - - if (mPatternDisplayMode == DisplayMode.Animate) { - - // figure out which circles to draw - - // + 1 so we pause on complete pattern - final int oneCycle = (count + 1) * MILLIS_PER_CIRCLE_ANIMATING; - final int spotInCycle = (int) (SystemClock.elapsedRealtime() - - mAnimatingPeriodStart) % oneCycle; - final int numCircles = spotInCycle / MILLIS_PER_CIRCLE_ANIMATING; - - clearPatternDrawLookup(); - for (int i = 0; i < numCircles; i++) { - final Cell cell = pattern.get(i); - drawLookup[cell.getRow()][cell.getColumn()] = true; - } - - // figure out in progress portion of ghosting line - - final boolean needToUpdateInProgressPoint = numCircles > 0 - && numCircles < count; - - if (needToUpdateInProgressPoint) { - final float percentageOfNextCircle = - ((float) (spotInCycle % MILLIS_PER_CIRCLE_ANIMATING)) / - MILLIS_PER_CIRCLE_ANIMATING; - - final Cell currentCell = pattern.get(numCircles - 1); - final float centerX = getCenterXForColumn(currentCell.column); - final float centerY = getCenterYForRow(currentCell.row); - - final Cell nextCell = pattern.get(numCircles); - final float dx = percentageOfNextCircle * - (getCenterXForColumn(nextCell.column) - centerX); - final float dy = percentageOfNextCircle * - (getCenterYForRow(nextCell.row) - centerY); - mInProgressX = centerX + dx; - mInProgressY = centerY + dy; - } - // TODO: Infinite loop here... - invalidate(); - } - - final float squareWidth = mSquareWidth; - final float squareHeight = mSquareHeight; - - float radius = (squareWidth * mDiameterFactor * 0.5f); - mPathPaint.setStrokeWidth(radius); - - final Path currentPath = mCurrentPath; - currentPath.rewind(); - - // TODO: the path should be created and cached every time we hit-detect a cell - // only the last segment of the path should be computed here - // draw the path of the pattern (unless the user is in progress, and - // we are in stealth mode) - final boolean drawPath = (!mInStealthMode || mPatternDisplayMode == DisplayMode.Wrong); - if (drawPath) { - boolean anyCircles = false; - for (int i = 0; i < count; i++) { - Cell cell = pattern.get(i); - - // only draw the part of the pattern stored in - // the lookup table (this is only different in the case - // of animation). - if (!drawLookup[cell.row][cell.column]) { - break; - } - anyCircles = true; - - float centerX = getCenterXForColumn(cell.column); - float centerY = getCenterYForRow(cell.row); - if (i == 0) { - currentPath.moveTo(centerX, centerY); - } else { - currentPath.lineTo(centerX, centerY); - } - } - - // add last in progress section - if ((mPatternInProgress || mPatternDisplayMode == DisplayMode.Animate) - && anyCircles) { - currentPath.lineTo(mInProgressX, mInProgressY); - } - canvas.drawPath(currentPath, mPathPaint); - } - - // draw the circles - final int paddingTop = mPaddingTop; - final int paddingLeft = mPaddingLeft; - - for (int i = 0; i < 3; i++) { - float topY = paddingTop + i * squareHeight; - //float centerY = mPaddingTop + i * mSquareHeight + (mSquareHeight / 2); - for (int j = 0; j < 3; j++) { - float leftX = paddingLeft + j * squareWidth; - drawCircle(canvas, (int) leftX, (int) topY, drawLookup[i][j]); - } - } - - // draw the arrows associated with the path (unless the user is in progress, and - // we are in stealth mode) - boolean oldFlag = (mPaint.getFlags() & Paint.FILTER_BITMAP_FLAG) != 0; - mPaint.setFilterBitmap(true); // draw with higher quality since we render with transforms - if (drawPath) { - for (int i = 0; i < count - 1; i++) { - Cell cell = pattern.get(i); - Cell next = pattern.get(i + 1); - - // only draw the part of the pattern stored in - // the lookup table (this is only different in the case - // of animation). - if (!drawLookup[next.row][next.column]) { - break; - } - - float leftX = paddingLeft + cell.column * squareWidth; - float topY = paddingTop + cell.row * squareHeight; - - drawArrow(canvas, leftX, topY, cell, next); - } - } - mPaint.setFilterBitmap(oldFlag); // restore default flag - } - - private void drawArrow(Canvas canvas, float leftX, float topY, Cell start, Cell end) { - boolean green = mPatternDisplayMode != DisplayMode.Wrong; - - final int endRow = end.row; - final int startRow = start.row; - final int endColumn = end.column; - final int startColumn = start.column; - - // offsets for centering the bitmap in the cell - final int offsetX = ((int) mSquareWidth - mBitmapWidth) / 2; - final int offsetY = ((int) mSquareHeight - mBitmapHeight) / 2; - - // compute transform to place arrow bitmaps at correct angle inside circle. - // This assumes that the arrow image is drawn at 12:00 with it's top edge - // coincident with the circle bitmap's top edge. - Bitmap arrow = green ? mBitmapArrowGreenUp : mBitmapArrowRedUp; - Matrix matrix = new Matrix(); - final int cellWidth = mBitmapCircleDefault.getWidth(); - final int cellHeight = mBitmapCircleDefault.getHeight(); - - // the up arrow bitmap is at 12:00, so find the rotation from x axis and add 90 degrees. - final float theta = (float) Math.atan2( - (double) (endRow - startRow), (double) (endColumn - startColumn)); - final float angle = (float) Math.toDegrees(theta) + 90.0f; - - // compose matrix - matrix.setTranslate(leftX + offsetX, topY + offsetY); // transform to cell position - matrix.preRotate(angle, cellWidth / 2.0f, cellHeight / 2.0f); // rotate about cell center - matrix.preTranslate((cellWidth - arrow.getWidth()) / 2.0f, 0.0f); // translate to 12:00 pos - canvas.drawBitmap(arrow, matrix, mPaint); - } - - /** - * @param canvas - * @param leftX - * @param topY - * @param partOfPattern Whether this circle is part of the pattern. - */ - private void drawCircle(Canvas canvas, int leftX, int topY, boolean partOfPattern) { - Bitmap outerCircle; - Bitmap innerCircle; - - if (!partOfPattern || (mInStealthMode && mPatternDisplayMode != DisplayMode.Wrong)) { - // unselected circle - outerCircle = mBitmapCircleDefault; - innerCircle = mBitmapBtnDefault; - } else if (mPatternInProgress) { - // user is in middle of drawing a pattern - outerCircle = mBitmapCircleGreen; - innerCircle = mBitmapBtnTouched; - } else if (mPatternDisplayMode == DisplayMode.Wrong) { - // the pattern is wrong - outerCircle = mBitmapCircleRed; - innerCircle = mBitmapBtnDefault; - } else if (mPatternDisplayMode == DisplayMode.Correct || - mPatternDisplayMode == DisplayMode.Animate) { - // the pattern is correct - outerCircle = mBitmapCircleGreen; - innerCircle = mBitmapBtnDefault; - } else { - throw new IllegalStateException("unknown display mode " + mPatternDisplayMode); - } - - final int width = mBitmapWidth; - final int height = mBitmapHeight; - - final float squareWidth = mSquareWidth; - final float squareHeight = mSquareHeight; - - int offsetX = (int) ((squareWidth - width) / 2f); - int offsetY = (int) ((squareHeight - height) / 2f); - - canvas.drawBitmap(outerCircle, leftX + offsetX, topY + offsetY, mPaint); - canvas.drawBitmap(innerCircle, leftX + offsetX, topY + offsetY, mPaint); - } - - @Override - protected Parcelable onSaveInstanceState() { - Parcelable superState = super.onSaveInstanceState(); - return new SavedState(superState, - LockPatternUtils.patternToString(mPattern), - mPatternDisplayMode.ordinal(), - mInputEnabled, mInStealthMode, mTactileFeedbackEnabled); - } - - @Override - protected void onRestoreInstanceState(Parcelable state) { - final SavedState ss = (SavedState) state; - super.onRestoreInstanceState(ss.getSuperState()); - setPattern( - DisplayMode.Correct, - LockPatternUtils.stringToPattern(ss.getSerializedPattern())); - mPatternDisplayMode = DisplayMode.values()[ss.getDisplayMode()]; - mInputEnabled = ss.isInputEnabled(); - mInStealthMode = ss.isInStealthMode(); - mTactileFeedbackEnabled = ss.isTactileFeedbackEnabled(); - } - - /** - * The parecelable for saving and restoring a lock pattern view. - */ - private static class SavedState extends BaseSavedState { - - private final String mSerializedPattern; - private final int mDisplayMode; - private final boolean mInputEnabled; - private final boolean mInStealthMode; - private final boolean mTactileFeedbackEnabled; - - /** - * Constructor called from {@link LockPatternView#onSaveInstanceState()} - */ - private SavedState(Parcelable superState, String serializedPattern, int displayMode, - boolean inputEnabled, boolean inStealthMode, boolean tactileFeedbackEnabled) { - super(superState); - mSerializedPattern = serializedPattern; - mDisplayMode = displayMode; - mInputEnabled = inputEnabled; - mInStealthMode = inStealthMode; - mTactileFeedbackEnabled = tactileFeedbackEnabled; - } - - /** - * Constructor called from {@link #CREATOR} - */ - private SavedState(Parcel in) { - super(in); - mSerializedPattern = in.readString(); - mDisplayMode = in.readInt(); - mInputEnabled = (Boolean) in.readValue(null); - mInStealthMode = (Boolean) in.readValue(null); - mTactileFeedbackEnabled = (Boolean) in.readValue(null); - } - - public String getSerializedPattern() { - return mSerializedPattern; - } - - public int getDisplayMode() { - return mDisplayMode; - } - - public boolean isInputEnabled() { - return mInputEnabled; - } - - public boolean isInStealthMode() { - return mInStealthMode; - } - - public boolean isTactileFeedbackEnabled(){ - return mTactileFeedbackEnabled; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - super.writeToParcel(dest, flags); - dest.writeString(mSerializedPattern); - dest.writeInt(mDisplayMode); - dest.writeValue(mInputEnabled); - dest.writeValue(mInStealthMode); - dest.writeValue(mTactileFeedbackEnabled); - } - - public static final Parcelable.Creator<SavedState> CREATOR = - new Creator<SavedState>() { - public SavedState createFromParcel(Parcel in) { - return new SavedState(in); - } - - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - } -} diff --git a/core/java/com/android/internal/widget/NumberPicker.java b/core/java/com/android/internal/widget/NumberPicker.java deleted file mode 100644 index 1647c20..0000000 --- a/core/java/com/android/internal/widget/NumberPicker.java +++ /dev/null @@ -1,406 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.widget; - -import android.content.Context; -import android.os.Handler; -import android.text.InputFilter; -import android.text.InputType; -import android.text.Spanned; -import android.text.method.NumberKeyListener; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.View.OnFocusChangeListener; -import android.view.View.OnLongClickListener; -import android.widget.TextView; -import android.widget.LinearLayout; - -import com.android.internal.R; - -public class NumberPicker extends LinearLayout implements OnClickListener, - OnFocusChangeListener, OnLongClickListener { - - public interface OnChangedListener { - void onChanged(NumberPicker picker, int oldVal, int newVal); - } - - public interface Formatter { - String toString(int value); - } - - /* - * Use a custom NumberPicker formatting callback to use two-digit - * minutes strings like "01". Keeping a static formatter etc. is the - * most efficient way to do this; it avoids creating temporary objects - * on every call to format(). - */ - public static final NumberPicker.Formatter TWO_DIGIT_FORMATTER = - new NumberPicker.Formatter() { - final StringBuilder mBuilder = new StringBuilder(); - final java.util.Formatter mFmt = new java.util.Formatter(mBuilder); - final Object[] mArgs = new Object[1]; - public String toString(int value) { - mArgs[0] = value; - mBuilder.delete(0, mBuilder.length()); - mFmt.format("%02d", mArgs); - return mFmt.toString(); - } - }; - - private final Handler mHandler; - private final Runnable mRunnable = new Runnable() { - public void run() { - if (mIncrement) { - changeCurrent(mCurrent + 1); - mHandler.postDelayed(this, mSpeed); - } else if (mDecrement) { - changeCurrent(mCurrent - 1); - mHandler.postDelayed(this, mSpeed); - } - } - }; - - private final TextView mText; - private final InputFilter mNumberInputFilter; - - private String[] mDisplayedValues; - private int mStart; - private int mEnd; - private int mCurrent; - private int mPrevious; - private OnChangedListener mListener; - private Formatter mFormatter; - private long mSpeed = 300; - - private boolean mIncrement; - private boolean mDecrement; - - public NumberPicker(Context context) { - this(context, null); - } - - public NumberPicker(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - @SuppressWarnings({"UnusedDeclaration"}) - public NumberPicker(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs); - setOrientation(VERTICAL); - LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - inflater.inflate(R.layout.number_picker, this, true); - mHandler = new Handler(); - InputFilter inputFilter = new NumberPickerInputFilter(); - mNumberInputFilter = new NumberRangeKeyListener(); - mIncrementButton = (NumberPickerButton) findViewById(R.id.increment); - mIncrementButton.setOnClickListener(this); - mIncrementButton.setOnLongClickListener(this); - mIncrementButton.setNumberPicker(this); - mDecrementButton = (NumberPickerButton) findViewById(R.id.decrement); - mDecrementButton.setOnClickListener(this); - mDecrementButton.setOnLongClickListener(this); - mDecrementButton.setNumberPicker(this); - - mText = (TextView) findViewById(R.id.timepicker_input); - mText.setOnFocusChangeListener(this); - mText.setFilters(new InputFilter[] {inputFilter}); - mText.setRawInputType(InputType.TYPE_CLASS_NUMBER); - - if (!isEnabled()) { - setEnabled(false); - } - } - - @Override - public void setEnabled(boolean enabled) { - super.setEnabled(enabled); - mIncrementButton.setEnabled(enabled); - mDecrementButton.setEnabled(enabled); - mText.setEnabled(enabled); - } - - public void setOnChangeListener(OnChangedListener listener) { - mListener = listener; - } - - public void setFormatter(Formatter formatter) { - mFormatter = formatter; - } - - /** - * Set the range of numbers allowed for the number picker. The current - * value will be automatically set to the start. - * - * @param start the start of the range (inclusive) - * @param end the end of the range (inclusive) - */ - public void setRange(int start, int end) { - mStart = start; - mEnd = end; - mCurrent = start; - updateView(); - } - - /** - * Set the range of numbers allowed for the number picker. The current - * value will be automatically set to the start. Also provide a mapping - * for values used to display to the user. - * - * @param start the start of the range (inclusive) - * @param end the end of the range (inclusive) - * @param displayedValues the values displayed to the user. - */ - public void setRange(int start, int end, String[] displayedValues) { - mDisplayedValues = displayedValues; - mStart = start; - mEnd = end; - mCurrent = start; - updateView(); - } - - public void setCurrent(int current) { - mCurrent = current; - updateView(); - } - - /** - * The speed (in milliseconds) at which the numbers will scroll - * when the the +/- buttons are longpressed. Default is 300ms. - */ - public void setSpeed(long speed) { - mSpeed = speed; - } - - public void onClick(View v) { - - /* The text view may still have focus so clear it's focus which will - * trigger the on focus changed and any typed values to be pulled. - */ - mText.clearFocus(); - - // now perform the increment/decrement - if (R.id.increment == v.getId()) { - changeCurrent(mCurrent + 1); - } else if (R.id.decrement == v.getId()) { - changeCurrent(mCurrent - 1); - } - } - - private String formatNumber(int value) { - return (mFormatter != null) - ? mFormatter.toString(value) - : String.valueOf(value); - } - - private void changeCurrent(int current) { - - // Wrap around the values if we go past the start or end - if (current > mEnd) { - current = mStart; - } else if (current < mStart) { - current = mEnd; - } - mPrevious = mCurrent; - mCurrent = current; - notifyChange(); - updateView(); - } - - private void notifyChange() { - if (mListener != null) { - mListener.onChanged(this, mPrevious, mCurrent); - } - } - - private void updateView() { - - /* If we don't have displayed values then use the - * current number else find the correct value in the - * displayed values for the current number. - */ - if (mDisplayedValues == null) { - mText.setText(formatNumber(mCurrent)); - } else { - mText.setText(mDisplayedValues[mCurrent - mStart]); - } - } - - private void validateCurrentView(CharSequence str) { - int val = getSelectedPos(str.toString()); - if ((val >= mStart) && (val <= mEnd)) { - mPrevious = mCurrent; - mCurrent = val; - notifyChange(); - } - updateView(); - } - - public void onFocusChange(View v, boolean hasFocus) { - - /* When focus is lost check that the text field - * has valid values. - */ - if (!hasFocus) { - String str = String.valueOf(((TextView) v).getText()); - if ("".equals(str)) { - - // Restore to the old value as we don't allow empty values - updateView(); - } else { - - // Check the new value and ensure it's in range - validateCurrentView(str); - } - } - } - - /** - * We start the long click here but rely on the {@link NumberPickerButton} - * to inform us when the long click has ended. - */ - public boolean onLongClick(View v) { - - /* The text view may still have focus so clear it's focus which will - * trigger the on focus changed and any typed values to be pulled. - */ - mText.clearFocus(); - - if (R.id.increment == v.getId()) { - mIncrement = true; - mHandler.post(mRunnable); - } else if (R.id.decrement == v.getId()) { - mDecrement = true; - mHandler.post(mRunnable); - } - return true; - } - - public void cancelIncrement() { - mIncrement = false; - } - - public void cancelDecrement() { - mDecrement = false; - } - - private static final char[] DIGIT_CHARACTERS = new char[] { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' - }; - - private NumberPickerButton mIncrementButton; - private NumberPickerButton mDecrementButton; - - private class NumberPickerInputFilter implements InputFilter { - public CharSequence filter(CharSequence source, int start, int end, - Spanned dest, int dstart, int dend) { - if (mDisplayedValues == null) { - return mNumberInputFilter.filter(source, start, end, dest, dstart, dend); - } - CharSequence filtered = String.valueOf(source.subSequence(start, end)); - String result = String.valueOf(dest.subSequence(0, dstart)) - + filtered - + dest.subSequence(dend, dest.length()); - String str = String.valueOf(result).toLowerCase(); - for (String val : mDisplayedValues) { - val = val.toLowerCase(); - if (val.startsWith(str)) { - return filtered; - } - } - return ""; - } - } - - private class NumberRangeKeyListener extends NumberKeyListener { - - // XXX This doesn't allow for range limits when controlled by a - // soft input method! - public int getInputType() { - return InputType.TYPE_CLASS_NUMBER; - } - - @Override - protected char[] getAcceptedChars() { - return DIGIT_CHARACTERS; - } - - @Override - public CharSequence filter(CharSequence source, int start, int end, - Spanned dest, int dstart, int dend) { - - CharSequence filtered = super.filter(source, start, end, dest, dstart, dend); - if (filtered == null) { - filtered = source.subSequence(start, end); - } - - String result = String.valueOf(dest.subSequence(0, dstart)) - + filtered - + dest.subSequence(dend, dest.length()); - - if ("".equals(result)) { - return result; - } - int val = getSelectedPos(result); - - /* Ensure the user can't type in a value greater - * than the max allowed. We have to allow less than min - * as the user might want to delete some numbers - * and then type a new number. - */ - if (val > mEnd) { - return ""; - } else { - return filtered; - } - } - } - - private int getSelectedPos(String str) { - if (mDisplayedValues == null) { - return Integer.parseInt(str); - } else { - for (int i = 0; i < mDisplayedValues.length; i++) { - - /* Don't force the user to type in jan when ja will do */ - str = str.toLowerCase(); - if (mDisplayedValues[i].toLowerCase().startsWith(str)) { - return mStart + i; - } - } - - /* The user might have typed in a number into the month field i.e. - * 10 instead of OCT so support that too. - */ - try { - return Integer.parseInt(str); - } catch (NumberFormatException e) { - - /* Ignore as if it's not a number we don't care */ - } - } - return mStart; - } - - /** - * @return the current value. - */ - public int getCurrent() { - return mCurrent; - } -}
\ No newline at end of file diff --git a/core/java/com/android/internal/widget/NumberPickerButton.java b/core/java/com/android/internal/widget/NumberPickerButton.java deleted file mode 100644 index 39f1e2c..0000000 --- a/core/java/com/android/internal/widget/NumberPickerButton.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.widget; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.widget.ImageButton; - -import com.android.internal.R; - -/** - * This class exists purely to cancel long click events. - */ -public class NumberPickerButton extends ImageButton { - - private NumberPicker mNumberPicker; - - public NumberPickerButton(Context context, AttributeSet attrs, - int defStyle) { - super(context, attrs, defStyle); - } - - public NumberPickerButton(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public NumberPickerButton(Context context) { - super(context); - } - - public void setNumberPicker(NumberPicker picker) { - mNumberPicker = picker; - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - cancelLongpressIfRequired(event); - return super.onTouchEvent(event); - } - - @Override - public boolean onTrackballEvent(MotionEvent event) { - cancelLongpressIfRequired(event); - return super.onTrackballEvent(event); - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - if ((keyCode == KeyEvent.KEYCODE_DPAD_CENTER) - || (keyCode == KeyEvent.KEYCODE_ENTER)) { - cancelLongpress(); - } - return super.onKeyUp(keyCode, event); - } - - private void cancelLongpressIfRequired(MotionEvent event) { - if ((event.getAction() == MotionEvent.ACTION_CANCEL) - || (event.getAction() == MotionEvent.ACTION_UP)) { - cancelLongpress(); - } - } - - private void cancelLongpress() { - if (R.id.increment == getId()) { - mNumberPicker.cancelIncrement(); - } else if (R.id.decrement == getId()) { - mNumberPicker.cancelDecrement(); - } - } -} diff --git a/core/java/com/android/internal/widget/TextProgressBar.java b/core/java/com/android/internal/widget/TextProgressBar.java deleted file mode 100644 index 5bf4601..0000000 --- a/core/java/com/android/internal/widget/TextProgressBar.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.widget; - -import android.content.Context; -import android.os.SystemClock; -import android.util.AttributeSet; -import android.util.Log; -import android.view.Gravity; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Chronometer; -import android.widget.Chronometer.OnChronometerTickListener; -import android.widget.ProgressBar; -import android.widget.RelativeLayout; -import android.widget.RemoteViews.RemoteView; - -/** - * Container that links together a {@link ProgressBar} and {@link Chronometer} - * as children. It subscribes to {@link Chronometer#OnChronometerTickListener} - * and updates the {@link ProgressBar} based on a preset finishing time. - * <p> - * This widget expects to contain two children with specific ids - * {@link android.R.id.progress} and {@link android.R.id.text1}. - * <p> - * If the {@link Chronometer} {@link android.R.attr#layout_width} is - * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}, then the - * {@link android.R.attr#gravity} will be used to automatically move it with - * respect to the {@link ProgressBar} position. For example, if - * {@link android.view.Gravity#LEFT} then the {@link Chronometer} will be placed - * just ahead of the leading edge of the {@link ProgressBar} position. - */ -@RemoteView -public class TextProgressBar extends RelativeLayout implements OnChronometerTickListener { - public static final String TAG = "TextProgressBar"; - - static final int CHRONOMETER_ID = android.R.id.text1; - static final int PROGRESSBAR_ID = android.R.id.progress; - - Chronometer mChronometer = null; - ProgressBar mProgressBar = null; - - long mDurationBase = -1; - int mDuration = -1; - - boolean mChronometerFollow = false; - int mChronometerGravity = Gravity.NO_GRAVITY; - - public TextProgressBar(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - public TextProgressBar(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public TextProgressBar(Context context) { - super(context); - } - - /** - * Catch any interesting children when they are added. - */ - @Override - public void addView(View child, int index, ViewGroup.LayoutParams params) { - super.addView(child, index, params); - - int childId = child.getId(); - if (childId == CHRONOMETER_ID && child instanceof Chronometer) { - mChronometer = (Chronometer) child; - mChronometer.setOnChronometerTickListener(this); - - // Check if Chronometer should move with with ProgressBar - mChronometerFollow = (params.width == ViewGroup.LayoutParams.WRAP_CONTENT); - mChronometerGravity = (mChronometer.getGravity() & Gravity.HORIZONTAL_GRAVITY_MASK); - - } else if (childId == PROGRESSBAR_ID && child instanceof ProgressBar) { - mProgressBar = (ProgressBar) child; - } - } - - /** - * Set the expected termination time of the running {@link Chronometer}. - * This value is used to adjust the {@link ProgressBar} against the elapsed - * time. - * <p> - * Call this <b>after</b> adjusting the {@link Chronometer} base, if - * necessary. - * - * @param durationBase Use the {@link SystemClock#elapsedRealtime} time - * base. - */ - @android.view.RemotableViewMethod - public void setDurationBase(long durationBase) { - mDurationBase = durationBase; - - if (mProgressBar == null || mChronometer == null) { - throw new RuntimeException("Expecting child ProgressBar with id " + - "'android.R.id.progress' and Chronometer id 'android.R.id.text1'"); - } - - // Update the ProgressBar maximum relative to Chronometer base - mDuration = (int) (durationBase - mChronometer.getBase()); - mProgressBar.setMax(mDuration); - - } - - /** - * Callback when {@link Chronometer} changes, indicating that we should - * update the {@link ProgressBar} and change the layout if necessary. - */ - public void onChronometerTick(Chronometer chronometer) { - if (mProgressBar == null) { - throw new RuntimeException( - "Expecting child ProgressBar with id 'android.R.id.progress'"); - } - - // Stop Chronometer if we're past duration - long now = SystemClock.elapsedRealtime(); - if (now >= mDurationBase) { - mChronometer.stop(); - } - - // Update the ProgressBar status - int remaining = (int) (mDurationBase - now); - mProgressBar.setProgress(mDuration - remaining); - - // Move the Chronometer if gravity is set correctly - if (mChronometerFollow) { - RelativeLayout.LayoutParams params; - - // Calculate estimate of ProgressBar leading edge position - params = (RelativeLayout.LayoutParams) mProgressBar.getLayoutParams(); - int contentWidth = mProgressBar.getWidth() - (params.leftMargin + params.rightMargin); - int leadingEdge = ((contentWidth * mProgressBar.getProgress()) / - mProgressBar.getMax()) + params.leftMargin; - - // Calculate any adjustment based on gravity - int adjustLeft = 0; - int textWidth = mChronometer.getWidth(); - if (mChronometerGravity == Gravity.RIGHT) { - adjustLeft = -textWidth; - } else if (mChronometerGravity == Gravity.CENTER_HORIZONTAL) { - adjustLeft = -(textWidth / 2); - } - - // Limit margin to keep text inside ProgressBar bounds - leadingEdge += adjustLeft; - int rightLimit = contentWidth - params.rightMargin - textWidth; - if (leadingEdge < params.leftMargin) { - leadingEdge = params.leftMargin; - } else if (leadingEdge > rightLimit) { - leadingEdge = rightLimit; - } - - params = (RelativeLayout.LayoutParams) mChronometer.getLayoutParams(); - params.leftMargin = leadingEdge; - - // Request layout to move Chronometer - mChronometer.requestLayout(); - - } - } -} diff --git a/core/java/com/android/internal/widget/VerticalTextSpinner.java b/core/java/com/android/internal/widget/VerticalTextSpinner.java deleted file mode 100644 index 50c528c..0000000 --- a/core/java/com/android/internal/widget/VerticalTextSpinner.java +++ /dev/null @@ -1,467 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.widget; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.text.TextPaint; -import android.util.AttributeSet; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.View; - - -public class VerticalTextSpinner extends View { - - private static final int SELECTOR_ARROW_HEIGHT = 15; - - private static final int TEXT_SPACING = 18; - private static final int TEXT_MARGIN_RIGHT = 25; - private static final int TEXT_SIZE = 22; - - /* Keep the calculations as this is really a for loop from - * -2 to 2 but precalculated so we don't have to do in the onDraw. - */ - private static final int TEXT1_Y = (TEXT_SIZE * (-2 + 2)) + (TEXT_SPACING * (-2 + 1)); - private static final int TEXT2_Y = (TEXT_SIZE * (-1 + 2)) + (TEXT_SPACING * (-1 + 1)); - private static final int TEXT3_Y = (TEXT_SIZE * (0 + 2)) + (TEXT_SPACING * (0 + 1)); - private static final int TEXT4_Y = (TEXT_SIZE * (1 + 2)) + (TEXT_SPACING * (1 + 1)); - private static final int TEXT5_Y = (TEXT_SIZE * (2 + 2)) + (TEXT_SPACING * (2 + 1)); - - private static final int SCROLL_MODE_NONE = 0; - private static final int SCROLL_MODE_UP = 1; - private static final int SCROLL_MODE_DOWN = 2; - - private static final long DEFAULT_SCROLL_INTERVAL_MS = 400; - private static final int SCROLL_DISTANCE = TEXT_SIZE + TEXT_SPACING; - private static final int MIN_ANIMATIONS = 4; - - private final Drawable mBackgroundFocused; - private final Drawable mSelectorFocused; - private final Drawable mSelectorNormal; - private final int mSelectorDefaultY; - private final int mSelectorMinY; - private final int mSelectorMaxY; - private final int mSelectorHeight; - private final TextPaint mTextPaintDark; - private final TextPaint mTextPaintLight; - - private int mSelectorY; - private Drawable mSelector; - private int mDownY; - private boolean isDraggingSelector; - private int mScrollMode; - private long mScrollInterval; - private boolean mIsAnimationRunning; - private boolean mStopAnimation; - private boolean mWrapAround = true; - - private int mTotalAnimatedDistance; - private int mNumberOfAnimations; - private long mDelayBetweenAnimations; - private int mDistanceOfEachAnimation; - - private String[] mTextList; - private int mCurrentSelectedPos; - private OnChangedListener mListener; - - private String mText1; - private String mText2; - private String mText3; - private String mText4; - private String mText5; - - public interface OnChangedListener { - void onChanged( - VerticalTextSpinner spinner, int oldPos, int newPos, String[] items); - } - - public VerticalTextSpinner(Context context) { - this(context, null); - } - - public VerticalTextSpinner(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public VerticalTextSpinner(Context context, AttributeSet attrs, - int defStyle) { - super(context, attrs, defStyle); - - mBackgroundFocused = context.getResources().getDrawable(com.android.internal.R.drawable.pickerbox_background); - mSelectorFocused = context.getResources().getDrawable(com.android.internal.R.drawable.pickerbox_selected); - mSelectorNormal = context.getResources().getDrawable(com.android.internal.R.drawable.pickerbox_unselected); - - mSelectorHeight = mSelectorFocused.getIntrinsicHeight(); - mSelectorDefaultY = (mBackgroundFocused.getIntrinsicHeight() - mSelectorHeight) / 2; - mSelectorMinY = 0; - mSelectorMaxY = mBackgroundFocused.getIntrinsicHeight() - mSelectorHeight; - - mSelector = mSelectorNormal; - mSelectorY = mSelectorDefaultY; - - mTextPaintDark = new TextPaint(Paint.ANTI_ALIAS_FLAG); - mTextPaintDark.setTextSize(TEXT_SIZE); - mTextPaintDark.setColor(context.getResources().getColor(com.android.internal.R.color.primary_text_light)); - - mTextPaintLight = new TextPaint(Paint.ANTI_ALIAS_FLAG); - mTextPaintLight.setTextSize(TEXT_SIZE); - mTextPaintLight.setColor(context.getResources().getColor(com.android.internal.R.color.secondary_text_dark)); - - mScrollMode = SCROLL_MODE_NONE; - mScrollInterval = DEFAULT_SCROLL_INTERVAL_MS; - calculateAnimationValues(); - } - - public void setOnChangeListener(OnChangedListener listener) { - mListener = listener; - } - - public void setItems(String[] textList) { - mTextList = textList; - calculateTextPositions(); - } - - public void setSelectedPos(int selectedPos) { - mCurrentSelectedPos = selectedPos; - calculateTextPositions(); - postInvalidate(); - } - - public void setScrollInterval(long interval) { - mScrollInterval = interval; - calculateAnimationValues(); - } - - public void setWrapAround(boolean wrap) { - mWrapAround = wrap; - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - - /* This is a bit confusing, when we get the key event - * DPAD_DOWN we actually roll the spinner up. When the - * key event is DPAD_UP we roll the spinner down. - */ - if ((keyCode == KeyEvent.KEYCODE_DPAD_UP) && canScrollDown()) { - mScrollMode = SCROLL_MODE_DOWN; - scroll(); - mStopAnimation = true; - return true; - } else if ((keyCode == KeyEvent.KEYCODE_DPAD_DOWN) && canScrollUp()) { - mScrollMode = SCROLL_MODE_UP; - scroll(); - mStopAnimation = true; - return true; - } - return super.onKeyDown(keyCode, event); - } - - private boolean canScrollDown() { - return (mCurrentSelectedPos > 0) || mWrapAround; - } - - private boolean canScrollUp() { - return ((mCurrentSelectedPos < (mTextList.length - 1)) || mWrapAround); - } - - @Override - protected void onFocusChanged(boolean gainFocus, int direction, - Rect previouslyFocusedRect) { - if (gainFocus) { - setBackgroundDrawable(mBackgroundFocused); - mSelector = mSelectorFocused; - } else { - setBackgroundDrawable(null); - mSelector = mSelectorNormal; - mSelectorY = mSelectorDefaultY; - } - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - final int action = event.getAction(); - final int y = (int) event.getY(); - - switch (action) { - case MotionEvent.ACTION_DOWN: - requestFocus(); - mDownY = y; - isDraggingSelector = (y >= mSelectorY) && (y <= (mSelectorY + mSelector.getIntrinsicHeight())); - break; - - case MotionEvent.ACTION_MOVE: - if (isDraggingSelector) { - int top = mSelectorDefaultY + (y - mDownY); - if (top <= mSelectorMinY && canScrollDown()) { - mSelectorY = mSelectorMinY; - mStopAnimation = false; - if (mScrollMode != SCROLL_MODE_DOWN) { - mScrollMode = SCROLL_MODE_DOWN; - scroll(); - } - } else if (top >= mSelectorMaxY && canScrollUp()) { - mSelectorY = mSelectorMaxY; - mStopAnimation = false; - if (mScrollMode != SCROLL_MODE_UP) { - mScrollMode = SCROLL_MODE_UP; - scroll(); - } - } else { - mSelectorY = top; - mStopAnimation = true; - } - } - break; - - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - default: - mSelectorY = mSelectorDefaultY; - mStopAnimation = true; - invalidate(); - break; - } - return true; - } - - @Override - protected void onDraw(Canvas canvas) { - - /* The bounds of the selector */ - final int selectorLeft = 0; - final int selectorTop = mSelectorY; - final int selectorRight = mMeasuredWidth; - final int selectorBottom = mSelectorY + mSelectorHeight; - - /* Draw the selector */ - mSelector.setBounds(selectorLeft, selectorTop, selectorRight, selectorBottom); - mSelector.draw(canvas); - - if (mTextList == null) { - - /* We're not setup with values so don't draw anything else */ - return; - } - - final TextPaint textPaintDark = mTextPaintDark; - if (hasFocus()) { - - /* The bounds of the top area where the text should be light */ - final int topLeft = 0; - final int topTop = 0; - final int topRight = selectorRight; - final int topBottom = selectorTop + SELECTOR_ARROW_HEIGHT; - - /* Assign a bunch of local finals for performance */ - final String text1 = mText1; - final String text2 = mText2; - final String text3 = mText3; - final String text4 = mText4; - final String text5 = mText5; - final TextPaint textPaintLight = mTextPaintLight; - - /* - * Draw the 1st, 2nd and 3rd item in light only, clip it so it only - * draws in the area above the selector - */ - canvas.save(); - canvas.clipRect(topLeft, topTop, topRight, topBottom); - drawText(canvas, text1, TEXT1_Y - + mTotalAnimatedDistance, textPaintLight); - drawText(canvas, text2, TEXT2_Y - + mTotalAnimatedDistance, textPaintLight); - drawText(canvas, text3, - TEXT3_Y + mTotalAnimatedDistance, textPaintLight); - canvas.restore(); - - /* - * Draw the 2nd, 3rd and 4th clipped to the selector bounds in dark - * paint - */ - canvas.save(); - canvas.clipRect(selectorLeft, selectorTop + SELECTOR_ARROW_HEIGHT, - selectorRight, selectorBottom - SELECTOR_ARROW_HEIGHT); - drawText(canvas, text2, TEXT2_Y - + mTotalAnimatedDistance, textPaintDark); - drawText(canvas, text3, - TEXT3_Y + mTotalAnimatedDistance, textPaintDark); - drawText(canvas, text4, - TEXT4_Y + mTotalAnimatedDistance, textPaintDark); - canvas.restore(); - - /* The bounds of the bottom area where the text should be light */ - final int bottomLeft = 0; - final int bottomTop = selectorBottom - SELECTOR_ARROW_HEIGHT; - final int bottomRight = selectorRight; - final int bottomBottom = mMeasuredHeight; - - /* - * Draw the 3rd, 4th and 5th in white text, clip it so it only draws - * in the area below the selector. - */ - canvas.save(); - canvas.clipRect(bottomLeft, bottomTop, bottomRight, bottomBottom); - drawText(canvas, text3, - TEXT3_Y + mTotalAnimatedDistance, textPaintLight); - drawText(canvas, text4, - TEXT4_Y + mTotalAnimatedDistance, textPaintLight); - drawText(canvas, text5, - TEXT5_Y + mTotalAnimatedDistance, textPaintLight); - canvas.restore(); - - } else { - drawText(canvas, mText3, TEXT3_Y, textPaintDark); - } - if (mIsAnimationRunning) { - if ((Math.abs(mTotalAnimatedDistance) + mDistanceOfEachAnimation) > SCROLL_DISTANCE) { - mTotalAnimatedDistance = 0; - if (mScrollMode == SCROLL_MODE_UP) { - int oldPos = mCurrentSelectedPos; - int newPos = getNewIndex(1); - if (newPos >= 0) { - mCurrentSelectedPos = newPos; - if (mListener != null) { - mListener.onChanged(this, oldPos, mCurrentSelectedPos, mTextList); - } - } - if (newPos < 0 || ((newPos >= mTextList.length - 1) && !mWrapAround)) { - mStopAnimation = true; - } - calculateTextPositions(); - } else if (mScrollMode == SCROLL_MODE_DOWN) { - int oldPos = mCurrentSelectedPos; - int newPos = getNewIndex(-1); - if (newPos >= 0) { - mCurrentSelectedPos = newPos; - if (mListener != null) { - mListener.onChanged(this, oldPos, mCurrentSelectedPos, mTextList); - } - } - if (newPos < 0 || (newPos == 0 && !mWrapAround)) { - mStopAnimation = true; - } - calculateTextPositions(); - } - if (mStopAnimation) { - final int previousScrollMode = mScrollMode; - - /* No longer scrolling, we wait till the current animation - * completes then we stop. - */ - mIsAnimationRunning = false; - mStopAnimation = false; - mScrollMode = SCROLL_MODE_NONE; - - /* If the current selected item is an empty string - * scroll past it. - */ - if ("".equals(mTextList[mCurrentSelectedPos])) { - mScrollMode = previousScrollMode; - scroll(); - mStopAnimation = true; - } - } - } else { - if (mScrollMode == SCROLL_MODE_UP) { - mTotalAnimatedDistance -= mDistanceOfEachAnimation; - } else if (mScrollMode == SCROLL_MODE_DOWN) { - mTotalAnimatedDistance += mDistanceOfEachAnimation; - } - } - if (mDelayBetweenAnimations > 0) { - postInvalidateDelayed(mDelayBetweenAnimations); - } else { - invalidate(); - } - } - } - - /** - * Called every time the text items or current position - * changes. We calculate store we don't have to calculate - * onDraw. - */ - private void calculateTextPositions() { - mText1 = getTextToDraw(-2); - mText2 = getTextToDraw(-1); - mText3 = getTextToDraw(0); - mText4 = getTextToDraw(1); - mText5 = getTextToDraw(2); - } - - private String getTextToDraw(int offset) { - int index = getNewIndex(offset); - if (index < 0) { - return ""; - } - return mTextList[index]; - } - - private int getNewIndex(int offset) { - int index = mCurrentSelectedPos + offset; - if (index < 0) { - if (mWrapAround) { - index += mTextList.length; - } else { - return -1; - } - } else if (index >= mTextList.length) { - if (mWrapAround) { - index -= mTextList.length; - } else { - return -1; - } - } - return index; - } - - private void scroll() { - if (mIsAnimationRunning) { - return; - } - mTotalAnimatedDistance = 0; - mIsAnimationRunning = true; - invalidate(); - } - - private void calculateAnimationValues() { - mNumberOfAnimations = (int) mScrollInterval / SCROLL_DISTANCE; - if (mNumberOfAnimations < MIN_ANIMATIONS) { - mNumberOfAnimations = MIN_ANIMATIONS; - mDistanceOfEachAnimation = SCROLL_DISTANCE / mNumberOfAnimations; - mDelayBetweenAnimations = 0; - } else { - mDistanceOfEachAnimation = SCROLL_DISTANCE / mNumberOfAnimations; - mDelayBetweenAnimations = mScrollInterval / mNumberOfAnimations; - } - } - - private void drawText(Canvas canvas, String text, int y, TextPaint paint) { - int width = (int) paint.measureText(text); - int x = getMeasuredWidth() - width - TEXT_MARGIN_RIGHT; - canvas.drawText(text, x, y, paint); - } - - public int getCurrentSelectedPos() { - return mCurrentSelectedPos; - } -} |