diff options
Diffstat (limited to 'core/java/com')
12 files changed, 364 insertions, 78 deletions
diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java index ee3f23b..41993c4 100644 --- a/core/java/com/android/internal/net/NetworkStatsFactory.java +++ b/core/java/com/android/internal/net/NetworkStatsFactory.java @@ -25,12 +25,14 @@ import android.net.NetworkStats; import android.os.SystemClock; import android.util.Slog; +import com.android.internal.util.ProcFileReader; import com.google.android.collect.Lists; import com.google.android.collect.Maps; import com.google.android.collect.Sets; import java.io.BufferedReader; import java.io.File; +import java.io.FileInputStream; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; @@ -107,6 +109,7 @@ public class NetworkStatsFactory { final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6); final NetworkStats.Entry entry = new NetworkStats.Entry(); + // TODO: transition to ProcFileReader // TODO: read directly from proc once headers are added final ArrayList<String> keys = Lists.newArrayList(KEY_IFACE, KEY_ACTIVE, KEY_SNAP_RX_BYTES, KEY_SNAP_RX_PACKETS, KEY_SNAP_TX_BYTES, KEY_SNAP_TX_PACKETS, KEY_RX_BYTES, @@ -257,71 +260,58 @@ public class NetworkStatsFactory { final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 24); final NetworkStats.Entry entry = new NetworkStats.Entry(); - // TODO: remove knownLines check once 5087722 verified - final HashSet<String> knownLines = Sets.newHashSet(); - // TODO: remove lastIdx check once 5270106 verified - int lastIdx; + int idx = 1; + int lastIdx = 1; - final ArrayList<String> keys = Lists.newArrayList(); - final ArrayList<String> values = Lists.newArrayList(); - final HashMap<String, String> parsed = Maps.newHashMap(); - - BufferedReader reader = null; - String line = null; + ProcFileReader reader = null; try { - reader = new BufferedReader(new FileReader(mStatsXtUid)); - - // parse first line as header - line = reader.readLine(); - splitLine(line, keys); - lastIdx = 1; - - // parse remaining lines - while ((line = reader.readLine()) != null) { - splitLine(line, values); - parseLine(keys, values, parsed); + // open and consume header line + reader = new ProcFileReader(new FileInputStream(mStatsXtUid)); + reader.finishLine(); - if (!knownLines.add(line)) { - throw new IllegalStateException("duplicate proc entry: " + line); - } - - final int idx = getParsedInt(parsed, KEY_IDX); + while (reader.hasMoreData()) { + idx = reader.nextInt(); if (idx != lastIdx + 1) { throw new IllegalStateException( "inconsistent idx=" + idx + " after lastIdx=" + lastIdx); } lastIdx = idx; - entry.iface = parsed.get(KEY_IFACE); - entry.uid = getParsedInt(parsed, KEY_UID); - entry.set = getParsedInt(parsed, KEY_COUNTER_SET); - entry.tag = kernelToTag(parsed.get(KEY_TAG_HEX)); - entry.rxBytes = getParsedLong(parsed, KEY_RX_BYTES); - entry.rxPackets = getParsedLong(parsed, KEY_RX_PACKETS); - entry.txBytes = getParsedLong(parsed, KEY_TX_BYTES); - entry.txPackets = getParsedLong(parsed, KEY_TX_PACKETS); + entry.iface = reader.nextString(); + entry.tag = kernelToTag(reader.nextString()); + entry.uid = reader.nextInt(); + entry.set = reader.nextInt(); + entry.rxBytes = reader.nextLong(); + entry.rxPackets = reader.nextLong(); + entry.txBytes = reader.nextLong(); + entry.txPackets = reader.nextLong(); if (limitUid == UID_ALL || limitUid == entry.uid) { stats.addValues(entry); } + + reader.finishLine(); } } catch (NullPointerException e) { - throw new IllegalStateException("problem parsing line: " + line, e); + throw new IllegalStateException("problem parsing idx " + idx, e); } catch (NumberFormatException e) { - throw new IllegalStateException("problem parsing line: " + line, e); + throw new IllegalStateException("problem parsing idx " + idx, e); } catch (IOException e) { - throw new IllegalStateException("problem parsing line: " + line, e); + throw new IllegalStateException("problem parsing idx " + idx, e); } finally { IoUtils.closeQuietly(reader); } + return stats; } + @Deprecated private static int getParsedInt(HashMap<String, String> parsed, String key) { final String value = parsed.get(key); return value != null ? Integer.parseInt(value) : 0; } + @Deprecated private static long getParsedLong(HashMap<String, String> parsed, String key) { final String value = parsed.get(key); return value != null ? Long.parseLong(value) : 0; @@ -330,6 +320,7 @@ public class NetworkStatsFactory { /** * Split given line into {@link ArrayList}. */ + @Deprecated private static void splitLine(String line, ArrayList<String> outSplit) { outSplit.clear(); @@ -343,6 +334,7 @@ public class NetworkStatsFactory { * Zip the two given {@link ArrayList} as key and value pairs into * {@link HashMap}. */ + @Deprecated private static void parseLine( ArrayList<String> keys, ArrayList<String> values, HashMap<String, String> outParsed) { outParsed.clear(); diff --git a/core/java/com/android/internal/textservice/ISpellCheckerSession.aidl b/core/java/com/android/internal/textservice/ISpellCheckerSession.aidl index 5a00603..3c61968 100644 --- a/core/java/com/android/internal/textservice/ISpellCheckerSession.aidl +++ b/core/java/com/android/internal/textservice/ISpellCheckerSession.aidl @@ -25,4 +25,5 @@ oneway interface ISpellCheckerSession { void onGetSuggestionsMultiple( in TextInfo[] textInfos, int suggestionsLimit, boolean multipleWords); void onCancel(); + void onClose(); } diff --git a/core/java/com/android/internal/util/ProcFileReader.java b/core/java/com/android/internal/util/ProcFileReader.java new file mode 100644 index 0000000..72e1f0f --- /dev/null +++ b/core/java/com/android/internal/util/ProcFileReader.java @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2011 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.util; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charsets; + +/** + * Reader that specializes in parsing {@code /proc/} files quickly. Walks + * through the stream using a single space {@code ' '} as token separator, and + * requires each line boundary to be explicitly acknowledged using + * {@link #finishLine()}. Assumes {@link Charsets#US_ASCII} encoding. + * <p> + * Currently doesn't support formats based on {@code \0}, tabs, or repeated + * delimiters. + */ +public class ProcFileReader implements Closeable { + private final InputStream mStream; + private final byte[] mBuffer; + + /** Write pointer in {@link #mBuffer}. */ + private int mTail; + /** Flag when last read token finished current line. */ + private boolean mLineFinished; + + public ProcFileReader(InputStream stream) throws IOException { + this(stream, 4096); + } + + public ProcFileReader(InputStream stream, int bufferSize) throws IOException { + mStream = stream; + mBuffer = new byte[bufferSize]; + + // read enough to answer hasMoreData + fillBuf(); + } + + /** + * Read more data from {@link #mStream} into internal buffer. + */ + private int fillBuf() throws IOException { + final int length = mBuffer.length - mTail; + if (length == 0) { + throw new IOException("attempting to fill already-full buffer"); + } + + final int read = mStream.read(mBuffer, mTail, length); + if (read != -1) { + mTail += read; + } + return read; + } + + /** + * Consume number of bytes from beginning of internal buffer. If consuming + * all remaining bytes, will attempt to {@link #fillBuf()}. + */ + private void consumeBuf(int count) throws IOException { + // TODO: consider moving to read pointer, but for now traceview says + // these copies aren't a bottleneck. + System.arraycopy(mBuffer, count, mBuffer, 0, mTail - count); + mTail -= count; + if (mTail == 0) { + fillBuf(); + } + } + + /** + * Find buffer index of next token delimiter, usually space or newline. Will + * fill buffer as needed. + */ + private int nextTokenIndex() throws IOException { + if (mLineFinished) { + throw new IOException("no tokens remaining on current line"); + } + + int i = 0; + do { + // scan forward for token boundary + for (; i < mTail; i++) { + final byte b = mBuffer[i]; + if (b == '\n') { + mLineFinished = true; + return i; + } + if (b == ' ') { + return i; + } + } + } while (fillBuf() > 0); + + throw new IOException("end of stream while looking for token boundary"); + } + + /** + * Check if stream has more data to be parsed. + */ + public boolean hasMoreData() { + return mTail > 0; + } + + /** + * Finish current line, skipping any remaining data. + */ + public void finishLine() throws IOException { + // last token already finished line; reset silently + if (mLineFinished) { + mLineFinished = false; + return; + } + + int i = 0; + do { + // scan forward for line boundary and consume + for (; i < mTail; i++) { + if (mBuffer[i] == '\n') { + consumeBuf(i + 1); + return; + } + } + } while (fillBuf() > 0); + + throw new IOException("end of stream while looking for line boundary"); + } + + /** + * Parse and return next token as {@link String}. + */ + public String nextString() throws IOException { + final int tokenIndex = nextTokenIndex(); + final String s = new String(mBuffer, 0, tokenIndex, Charsets.US_ASCII); + consumeBuf(tokenIndex + 1); + return s; + } + + /** + * Parse and return next token as base-10 encoded {@code long}. + */ + public long nextLong() throws IOException { + final int tokenIndex = nextTokenIndex(); + final boolean negative = mBuffer[0] == '-'; + + // TODO: refactor into something like IntegralToString + long result = 0; + for (int i = negative ? 1 : 0; i < tokenIndex; i++) { + final int digit = mBuffer[i] - '0'; + if (digit < 0 || digit > 9) { + throw invalidLong(tokenIndex); + } + + // always parse as negative number and apply sign later; this + // correctly handles MIN_VALUE which is "larger" than MAX_VALUE. + final long next = result * 10 - digit; + if (next > result) { + throw invalidLong(tokenIndex); + } + result = next; + } + + consumeBuf(tokenIndex + 1); + return negative ? result : -result; + } + + private NumberFormatException invalidLong(int tokenIndex) { + return new NumberFormatException( + "invalid long: " + new String(mBuffer, 0, tokenIndex, Charsets.US_ASCII)); + } + + /** + * Parse and return next token as base-10 encoded {@code int}. + */ + public int nextInt() throws IOException { + final long value = nextLong(); + if (value > Integer.MAX_VALUE || value < Integer.MIN_VALUE) { + throw new NumberFormatException("parsed value larger than integer"); + } + return (int) value; + } + + public void close() throws IOException { + mStream.close(); + } +} diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java index e245960..a10d241 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuItemView.java +++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java @@ -18,6 +18,7 @@ package com.android.internal.view.menu; import android.content.Context; import android.content.res.Resources; +import android.content.res.TypedArray; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.text.TextUtils; @@ -46,8 +47,8 @@ public class ActionMenuItemView extends LinearLayout private ImageButton mImageButton; private Button mTextButton; private boolean mAllowTextWithIcon; - private boolean mShowTextAllCaps; private boolean mExpandedFormat; + private int mMinWidth; public ActionMenuItemView(Context context) { this(context, null); @@ -62,7 +63,11 @@ public class ActionMenuItemView extends LinearLayout final Resources res = context.getResources(); mAllowTextWithIcon = res.getBoolean( com.android.internal.R.bool.config_allowActionMenuItemTextWithIcon); - mShowTextAllCaps = res.getBoolean(com.android.internal.R.bool.config_actionMenuItemAllCaps); + TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.ActionMenuItemView, 0, 0); + mMinWidth = a.getDimensionPixelSize( + com.android.internal.R.styleable.ActionMenuItemView_minWidth, 0); + a.recycle(); } @Override @@ -228,4 +233,21 @@ public class ActionMenuItemView extends LinearLayout cheatSheet.show(); return true; } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + final int widthMode = MeasureSpec.getMode(widthMeasureSpec); + final int specSize = MeasureSpec.getSize(widthMeasureSpec); + final int oldMeasuredWidth = getMeasuredWidth(); + final int targetWidth = widthMode == MeasureSpec.AT_MOST ? Math.min(specSize, mMinWidth) + : mMinWidth; + + if (widthMode != MeasureSpec.EXACTLY && mMinWidth > 0 && oldMeasuredWidth < targetWidth) { + // Remeasure at exactly the minimum width. + super.onMeasure(MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY), + heightMeasureSpec); + } + } } diff --git a/core/java/com/android/internal/view/menu/BaseMenuPresenter.java b/core/java/com/android/internal/view/menu/BaseMenuPresenter.java index 1e06b5a..db0d6dd 100644 --- a/core/java/com/android/internal/view/menu/BaseMenuPresenter.java +++ b/core/java/com/android/internal/view/menu/BaseMenuPresenter.java @@ -91,7 +91,14 @@ public abstract class BaseMenuPresenter implements MenuPresenter { MenuItemImpl item = visibleItems.get(i); if (shouldIncludeItem(childIndex, item)) { final View convertView = parent.getChildAt(childIndex); + final MenuItemImpl oldItem = convertView instanceof MenuView.ItemView ? + ((MenuView.ItemView) convertView).getItemData() : null; final View itemView = getItemView(item, convertView, parent); + if (item != oldItem) { + // Don't let old states linger with new data. + itemView.setPressed(false); + itemView.jumpDrawablesToCurrentState(); + } if (itemView != convertView) { addItemView(itemView, childIndex); } diff --git a/core/java/com/android/internal/view/menu/ExpandedMenuView.java b/core/java/com/android/internal/view/menu/ExpandedMenuView.java index 723ece4..47058ad 100644 --- a/core/java/com/android/internal/view/menu/ExpandedMenuView.java +++ b/core/java/com/android/internal/view/menu/ExpandedMenuView.java @@ -63,11 +63,6 @@ public final class ExpandedMenuView extends ListView implements ItemInvoker, Men setChildrenDrawingCacheEnabled(false); } - @Override - protected boolean recycleOnMeasure() { - return false; - } - public boolean invokeItem(MenuItemImpl item) { return mMenu.performItemAction(item, 0); } diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java index a1e16d4..df579c6 100644 --- a/core/java/com/android/internal/view/menu/ListMenuItemView.java +++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java @@ -34,6 +34,7 @@ import android.widget.TextView; * The item view for each item in the ListView-based MenuViews. */ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView { + private static final String TAG = "ListMenuItemView"; private MenuItemImpl mItemData; private ImageView mIconView; @@ -121,27 +122,25 @@ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView } public void setCheckable(boolean checkable) { - if (!checkable && mRadioButton == null && mCheckBox == null) { return; } - if (mRadioButton == null) { - insertRadioButton(); - } - if (mCheckBox == null) { - insertCheckBox(); - } - // Depending on whether its exclusive check or not, the checkbox or // radio button will be the one in use (and the other will be otherCompoundButton) final CompoundButton compoundButton; final CompoundButton otherCompoundButton; if (mItemData.isExclusiveCheckable()) { + if (mRadioButton == null) { + insertRadioButton(); + } compoundButton = mRadioButton; otherCompoundButton = mCheckBox; } else { + if (mCheckBox == null) { + insertCheckBox(); + } compoundButton = mCheckBox; otherCompoundButton = mRadioButton; } @@ -155,12 +154,12 @@ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView } // Make sure the other compound button isn't visible - if (otherCompoundButton.getVisibility() != GONE) { + if (otherCompoundButton != null && otherCompoundButton.getVisibility() != GONE) { otherCompoundButton.setVisibility(GONE); } } else { - mCheckBox.setVisibility(GONE); - mRadioButton.setVisibility(GONE); + if (mCheckBox != null) mCheckBox.setVisibility(GONE); + if (mRadioButton != null) mRadioButton.setVisibility(GONE); } } diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java index e131242..b689f53 100644 --- a/core/java/com/android/internal/widget/ActionBarView.java +++ b/core/java/com/android/internal/widget/ActionBarView.java @@ -518,6 +518,7 @@ public class ActionBarView extends AbsActionBarView { public void setHomeButtonEnabled(boolean enable) { mHomeLayout.setEnabled(enable); + mHomeLayout.setFocusable(enable); // Make sure the home button has an accurate content description for accessibility. if (!enable) { mHomeLayout.setContentDescription(null); @@ -536,7 +537,7 @@ public class ActionBarView extends AbsActionBarView { if ((flagsChanged & DISPLAY_RELAYOUT_MASK) != 0) { final boolean showHome = (options & ActionBar.DISPLAY_SHOW_HOME) != 0; - final int vis = showHome ? VISIBLE : GONE; + final int vis = showHome && mExpandedActionView == null ? VISIBLE : GONE; mHomeLayout.setVisibility(vis); if ((flagsChanged & ActionBar.DISPLAY_HOME_AS_UP) != 0) { diff --git a/core/java/com/android/internal/widget/DigitalClock.java b/core/java/com/android/internal/widget/DigitalClock.java index 18a4794..daefc9a 100644 --- a/core/java/com/android/internal/widget/DigitalClock.java +++ b/core/java/com/android/internal/widget/DigitalClock.java @@ -106,7 +106,8 @@ public class DigitalClock extends RelativeLayout { private String mAmString, mPmString; AmPm(View parent, Typeface tf) { - mAmPmTextView = (TextView) parent.findViewById(R.id.am_pm); + // No longer used, uncomment if we decide to use AM/PM indicator again + // mAmPmTextView = (TextView) parent.findViewById(R.id.am_pm); if (mAmPmTextView != null && tf != null) { mAmPmTextView.setTypeface(tf); } @@ -168,6 +169,8 @@ public class DigitalClock extends RelativeLayout { /* The time display consists of two tones. That's why we have two overlapping text views. */ mTimeDisplayBackground = (TextView) findViewById(R.id.timeDisplayBackground); mTimeDisplayBackground.setTypeface(sBackgroundFont); + mTimeDisplayBackground.setVisibility(View.INVISIBLE); + mTimeDisplayForeground = (TextView) findViewById(R.id.timeDisplayForeground); mTimeDisplayForeground.setTypeface(sForegroundFont); mAmPm = new AmPm(this, null); diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 17b8acf..89f9d4e 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -25,14 +25,11 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; -import android.hardware.Camera; -import android.hardware.Camera.CameraInfo; import android.os.FileObserver; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; -import android.os.SystemProperties; import android.os.storage.IMountService; import android.provider.Settings; import android.security.KeyStore; @@ -41,7 +38,6 @@ import android.text.TextUtils; import android.util.Log; import android.view.View; import android.widget.Button; -import android.widget.TextView; import java.io.File; import java.io.FileNotFoundException; @@ -968,6 +964,11 @@ public class LockPatternUtils { com.android.internal.R.bool.config_enable_puk_unlock_screen); } + public boolean isEmergencyCallEnabledWhileSimLocked() { + return mContext.getResources().getBoolean( + com.android.internal.R.bool.config_enable_emergency_call_while_sim_locked); + } + /** * @return A formatted string of the next alarm (for showing on the lock screen), * or null if there is no next alarm. @@ -1031,12 +1032,10 @@ public class LockPatternUtils { * {@link TelephonyManager#CALL_STATE_IDLE} * {@link TelephonyManager#CALL_STATE_RINGING} * {@link TelephonyManager#CALL_STATE_OFFHOOK} - * @param showIfCapable indicates whether the button should be shown if emergency calls are - * possible on the device + * @param shown indicates whether the given screen wants the emergency button to show at all */ - public void updateEmergencyCallButtonState(Button button, int phoneState, - boolean showIfCapable) { - if (isEmergencyCallCapable() && showIfCapable) { + public void updateEmergencyCallButtonState(Button button, int phoneState, boolean shown) { + if (isEmergencyCallCapable() && shown) { button.setVisibility(View.VISIBLE); } else { button.setVisibility(View.GONE); diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java index a2fc6e2..0d9cf9a 100644 --- a/core/java/com/android/internal/widget/LockPatternView.java +++ b/core/java/com/android/internal/widget/LockPatternView.java @@ -463,7 +463,7 @@ public class LockPatternView extends View { result = desired; break; case MeasureSpec.AT_MOST: - result = Math.min(specSize, desired); + result = Math.max(specSize, desired); break; case MeasureSpec.EXACTLY: default: diff --git a/core/java/com/android/internal/widget/WaveView.java b/core/java/com/android/internal/widget/WaveView.java index 288865f..2d89234 100644 --- a/core/java/com/android/internal/widget/WaveView.java +++ b/core/java/com/android/internal/widget/WaveView.java @@ -26,10 +26,13 @@ import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.drawable.BitmapDrawable; import android.os.Vibrator; +import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; import com.android.internal.R; @@ -64,6 +67,18 @@ public class WaveView extends View implements ValueAnimator.AnimatorUpdateListen private static final long DELAY_INCREMENT2 = 12; // increment per wave while not tracking private static final long WAVE_DELAY = WAVE_DURATION / WAVE_COUNT; // initial propagation delay + /** + * The scale by which to multiply the unlock handle width to compute the radius + * in which it can be grabbed when accessibility is disabled. + */ + private static final float GRAB_HANDLE_RADIUS_SCALE_ACCESSIBILITY_DISABLED = 0.5f; + + /** + * The scale by which to multiply the unlock handle width to compute the radius + * in which it can be grabbed when accessibility is enabled (more generous). + */ + private static final float GRAB_HANDLE_RADIUS_SCALE_ACCESSIBILITY_ENABLED = 1.0f; + private Vibrator mVibrator; private OnTriggerListener mOnTriggerListener; private ArrayList<DrawableHolder> mDrawables = new ArrayList<DrawableHolder>(3); @@ -451,6 +466,27 @@ public class WaveView extends View implements ValueAnimator.AnimatorUpdateListen }; @Override + public boolean onHoverEvent(MotionEvent event) { + if (AccessibilityManager.getInstance(mContext).isTouchExplorationEnabled()) { + final int action = event.getAction(); + switch (action) { + case MotionEvent.ACTION_HOVER_ENTER: + event.setAction(MotionEvent.ACTION_DOWN); + break; + case MotionEvent.ACTION_HOVER_MOVE: + event.setAction(MotionEvent.ACTION_MOVE); + break; + case MotionEvent.ACTION_HOVER_EXIT: + event.setAction(MotionEvent.ACTION_UP); + break; + } + onTouchEvent(event); + event.setAction(action); + } + return super.onHoverEvent(event); + } + + @Override public boolean onTouchEvent(MotionEvent event) { final int action = event.getAction(); mMouseX = event.getX(); @@ -460,21 +496,12 @@ public class WaveView extends View implements ValueAnimator.AnimatorUpdateListen case MotionEvent.ACTION_DOWN: removeCallbacks(mLockTimerActions); mFingerDown = true; - setGrabbedState(OnTriggerListener.CENTER_HANDLE); - { - float x = mMouseX - mUnlockHalo.getX(); - float y = mMouseY - mUnlockHalo.getY(); - float dist = (float) Math.hypot(x, y); - if (dist < mUnlockHalo.getWidth()*0.5f) { - if (mLockState == STATE_READY) { - mLockState = STATE_START_ATTEMPT; - } - } - } + tryTransitionToStartAttemptState(event); handled = true; break; case MotionEvent.ACTION_MOVE: + tryTransitionToStartAttemptState(event); handled = true; break; @@ -502,6 +529,47 @@ public class WaveView extends View implements ValueAnimator.AnimatorUpdateListen } /** + * Tries to transition to start attempt state. + * + * @param event A motion event. + */ + private void tryTransitionToStartAttemptState(MotionEvent event) { + final float dx = event.getX() - mUnlockHalo.getX(); + final float dy = event.getY() - mUnlockHalo.getY(); + float dist = (float) Math.hypot(dx, dy); + if (dist <= getScaledGrabHandleRadius()) { + setGrabbedState(OnTriggerListener.CENTER_HANDLE); + if (mLockState == STATE_READY) { + mLockState = STATE_START_ATTEMPT; + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + announceUnlockHandle(); + } + } + } + } + + /** + * @return The radius in which the handle is grabbed scaled based on + * whether accessibility is enabled. + */ + private float getScaledGrabHandleRadius() { + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + return GRAB_HANDLE_RADIUS_SCALE_ACCESSIBILITY_ENABLED * mUnlockHalo.getWidth(); + } else { + return GRAB_HANDLE_RADIUS_SCALE_ACCESSIBILITY_DISABLED * mUnlockHalo.getWidth(); + } + } + + /** + * Announces the unlock handle if accessibility is enabled. + */ + private void announceUnlockHandle() { + setContentDescription(mContext.getString(R.string.description_target_unlock_tablet)); + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); + setContentDescription(null); + } + + /** * Triggers haptic feedback. */ private synchronized void vibrate(long duration) { |
