From f058340b2f1c3d8114c48581680b4294122fe371 Mon Sep 17 00:00:00 2001 From: Svetoslav Ganov Date: Fri, 26 Aug 2011 20:12:02 -0700 Subject: [PATCH] Adding accessibility support to the slide lock screen bug:5210233 Change-Id: I93e876524ae6aaf75aadbe6a21c5c17d41a705f0 --- api/current.txt | 2 + .../widget/multiwaveview/MultiWaveView.java | 191 +++++++++++++++++- .../res/layout/keyguard_screen_tab_unlock.xml | 2 + .../keyguard_screen_tab_unlock_land.xml | 2 + core/res/res/values-land/arrays.xml | 44 +++- core/res/res/values/arrays.xml | 46 ++++- core/res/res/values/attrs.xml | 6 + core/res/res/values/public.xml | 4 + core/res/res/values/strings.xml | 23 +++ 9 files changed, 312 insertions(+), 8 deletions(-) diff --git a/api/current.txt b/api/current.txt index 312041695b942..dfc17ba7bde8a 100644 --- a/api/current.txt +++ b/api/current.txt @@ -370,6 +370,7 @@ package android { field public static final int dialogTitle = 16843250; // 0x10101f2 field public static final int digits = 16843110; // 0x1010166 field public static final int direction = 16843217; // 0x10101d1 + field public static final int directionDescriptions = 16843695; // 0x10103af field public static final int directionPriority = 16843218; // 0x10101d2 field public static final int disableDependentsState = 16843249; // 0x10101f1 field public static final int disabledAlpha = 16842803; // 0x1010033 @@ -938,6 +939,7 @@ package android { field public static final int tag = 16842961; // 0x10100d1 field public static final int targetActivity = 16843266; // 0x1010202 field public static final int targetClass = 16842799; // 0x101002f + field public static final int targetDescriptions = 16843694; // 0x10103ae field public static final int targetDrawables = 16843654; // 0x1010386 field public static final int targetPackage = 16842785; // 0x1010021 field public static final int targetSdkVersion = 16843376; // 0x1010270 diff --git a/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java b/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java index ec926e4fdfe9f..76bc535e865c6 100644 --- a/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java +++ b/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java @@ -16,8 +16,6 @@ package com.android.internal.widget.multiwaveview; -import java.util.ArrayList; - import android.animation.Animator; import android.animation.Animator.AnimatorListener; import android.animation.AnimatorListenerAdapter; @@ -31,15 +29,20 @@ import android.graphics.Canvas; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.os.Vibrator; +import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.util.TypedValue; import android.view.MotionEvent; import android.view.View; -import android.view.View.MeasureSpec; +import android.view.ViewConfiguration; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; import com.android.internal.R; +import java.util.ArrayList; + /** * A special widget containing a center and outer ring. Moving the center ring to the outer ring * causes an event that can be caught by implementing OnTriggerListener. @@ -82,6 +85,8 @@ public class MultiWaveView extends View { private ArrayList mChevronDrawables = new ArrayList(); private ArrayList mChevronAnimations = new ArrayList(); private ArrayList mTargetAnimations = new ArrayList(); + private ArrayList mTargetDescriptions; + private ArrayList mDirectionDescriptions; private Tweener mHandleAnimation; private OnTriggerListener mOnTriggerListener; private TargetDrawable mHandleDrawable; @@ -103,6 +108,9 @@ public class MultiWaveView extends View { private boolean mDragging; private int mNewTargetResources; + private boolean mWaveHovered = false; + private long mLastHoverExitTimeMillis = 0; + private AnimatorListener mResetListener = new AnimatorListenerAdapter() { public void onAnimationEnd(Animator animator) { switchToState(STATE_IDLE, mWaveCenterX, mWaveCenterY); @@ -128,6 +136,8 @@ public class MultiWaveView extends View { } }; private int mTargetResourceId; + private int mTargetDescriptionsResourceId; + private int mDirectionDescriptionsResourceId; public MultiWaveView(Context context) { this(context, null); @@ -177,6 +187,25 @@ public class MultiWaveView extends View { throw new IllegalStateException("Must specify at least one target drawable"); } + // Read array of target descriptions + if (a.getValue(R.styleable.MultiWaveView_targetDescriptions, outValue)) { + final int resourceId = outValue.resourceId; + if (resourceId == 0) { + throw new IllegalStateException("Must specify target descriptions"); + } + setTargetDescriptionsResourceId(resourceId); + } + + // Read array of direction descriptions + if (a.getValue(R.styleable.MultiWaveView_directionDescriptions, outValue)) { + final int resourceId = outValue.resourceId; + if (resourceId == 0) { + throw new IllegalStateException("Must specify direction descriptions"); + } + setDirectionDescriptionsResourceId(resourceId); + } + + a.recycle(); setVibrateEnabled(mVibrationDuration > 0); } @@ -247,6 +276,9 @@ public class MultiWaveView extends View { showTargets(true); mHandleDrawable.setState(TargetDrawable.STATE_ACTIVE); setGrabbedState(OnTriggerListener.CENTER_HANDLE); + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + announceTargets(); + } break; case STATE_TRACKING: @@ -347,6 +379,13 @@ public class MultiWaveView extends View { } } + private void dispatchGrabbedEvent(int whichHandler) { + vibrate(); + if (mOnTriggerListener != null) { + mOnTriggerListener.onGrabbed(this, whichHandler); + } + } + private void doFinish() { final int activeTarget = mActiveTarget; boolean targetHit = activeTarget != -1; @@ -475,6 +514,7 @@ public class MultiWaveView extends View { Drawable drawable = array.getDrawable(i); targetDrawables.add(new TargetDrawable(res, drawable)); } + array.recycle(); mTargetResourceId = resourceId; mTargetDrawables = targetDrawables; updateTargetPositions(); @@ -498,6 +538,48 @@ public class MultiWaveView extends View { return mTargetResourceId; } + /** + * Sets the resource id specifying the target descriptions for accessibility. + * + * @param resourceId The resource id. + */ + public void setTargetDescriptionsResourceId(int resourceId) { + mTargetDescriptionsResourceId = resourceId; + if (mTargetDescriptions != null) { + mTargetDescriptions.clear(); + } + } + + /** + * Gets the resource id specifying the target descriptions for accessibility. + * + * @return The resource id. + */ + public int getTargetDescriptionsResourceId() { + return mTargetDescriptionsResourceId; + } + + /** + * Sets the resource id specifying the target direction descriptions for accessibility. + * + * @param resourceId The resource id. + */ + public void setDirectionDescriptionsResourceId(int resourceId) { + mDirectionDescriptionsResourceId = resourceId; + if (mDirectionDescriptions != null) { + mDirectionDescriptions.clear(); + } + } + + /** + * Gets the resource id specifying the target direction descriptions. + * + * @return The resource id. + */ + public int getDirectionDescriptionsResourceId() { + return mDirectionDescriptionsResourceId; + } + /** * Enable or disable vibrate on touch. * @@ -593,6 +675,43 @@ public class MultiWaveView extends View { } } + @Override + public boolean onHoverEvent(MotionEvent event) { + if (AccessibilityManager.getInstance(mContext).isTouchExplorationEnabled()) { + final int action = event.getAction(); + switch (action) { + case MotionEvent.ACTION_HOVER_ENTER: + case MotionEvent.ACTION_HOVER_MOVE: + final float dx = event.getX() - mWaveCenterX; + final float dy = event.getY() - mWaveCenterY; + if (dist2(dx,dy) <= square(mTapRadius)) { + if (!mWaveHovered) { + mWaveHovered = true; + final long timeSinceLastHoverExitMillis = + event.getEventTime() - mLastHoverExitTimeMillis; + final long recurringEventsInterval = + ViewConfiguration.getSendRecurringAccessibilityEventsInterval(); + if (timeSinceLastHoverExitMillis > recurringEventsInterval) { + String text = + mContext.getString(R.string.content_description_sliding_handle); + announceText(text); + } + } + } else { + mWaveHovered = false; + } + break; + case MotionEvent.ACTION_HOVER_EXIT: + mLastHoverExitTimeMillis = event.getEventTime(); + mWaveHovered = false; + break; + default: + mWaveHovered = false; + } + } + return super.onHoverEvent(event); + } + private void handleUp(MotionEvent event) { if (DEBUG && mDragging) Log.v(TAG, "** Handle RELEASE"); switchToState(STATE_FINISH, event.getX(), event.getY()); @@ -663,7 +782,11 @@ public class MultiWaveView extends View { invalidateGlobalRegion(mHandleDrawable); if (mActiveTarget != activeTarget && activeTarget != -1) { - vibrate(); + dispatchGrabbedEvent(activeTarget); + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + String targetContentDescription = getTargetDescription(activeTarget); + announceText(targetContentDescription); + } } mActiveTarget = activeTarget; } @@ -771,4 +894,62 @@ public class MultiWaveView extends View { return dx*dx + dy*dy; } -} \ No newline at end of file + private void announceTargets() { + StringBuilder utterance = new StringBuilder(); + final int targetCount = mTargetDrawables.size(); + for (int i = 0; i < targetCount; i++) { + String targetDescription = getTargetDescription(i); + String directionDescription = getDirectionDescription(i); + if (!TextUtils.isEmpty(targetDescription) + && !TextUtils.isEmpty(directionDescription)) { + utterance.append(targetDescription); + utterance.append(" "); + utterance.append(directionDescription); + utterance.append("."); + } + } + announceText(utterance.toString()); + } + + private void announceText(String text) { + setContentDescription(text); + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); + setContentDescription(null); + } + + private String getTargetDescription(int index) { + if (mTargetDescriptions == null || mTargetDescriptions.isEmpty()) { + mTargetDescriptions = loadDescriptions(mTargetDescriptionsResourceId); + if (mTargetDrawables.size() != mTargetDescriptions.size()) { + Log.w(TAG, "The number of target drawables must be" + + " euqal to the number of target descriptions."); + return null; + } + } + return mTargetDescriptions.get(index); + } + + private String getDirectionDescription(int index) { + if (mDirectionDescriptions == null || mDirectionDescriptions.isEmpty()) { + mDirectionDescriptions = loadDescriptions(mDirectionDescriptionsResourceId); + if (mTargetDrawables.size() != mDirectionDescriptions.size()) { + Log.w(TAG, "The number of target drawables must be" + + " euqal to the number of direction descriptions."); + return null; + } + } + return mDirectionDescriptions.get(index); + } + + private ArrayList loadDescriptions(int resourceId) { + TypedArray array = getContext().getResources().obtainTypedArray(resourceId); + final int count = array.length(); + ArrayList targetContentDescriptions = new ArrayList(count); + for (int i = 0; i < count; i++) { + String contentDescription = array.getString(i); + targetContentDescriptions.add(contentDescription); + } + array.recycle(); + return targetContentDescriptions; + } +} diff --git a/core/res/res/layout/keyguard_screen_tab_unlock.xml b/core/res/res/layout/keyguard_screen_tab_unlock.xml index a42d6cb94a1b4..4c8c0d1ef3407 100644 --- a/core/res/res/layout/keyguard_screen_tab_unlock.xml +++ b/core/res/res/layout/keyguard_screen_tab_unlock.xml @@ -130,6 +130,8 @@ android:layout_alignParentBottom="true" android:targetDrawables="@array/lockscreen_targets_with_camera" + android:targetDescriptions="@array/lockscreen_target_descriptions_with_camera" + android:directionDescriptions="@array/lockscreen_direction_descriptions_with_camera" android:handleDrawable="@drawable/ic_lockscreen_handle" android:waveDrawable="@drawable/ic_lockscreen_outerring" android:outerRadius="@dimen/multiwaveview_target_placement_radius" diff --git a/core/res/res/layout/keyguard_screen_tab_unlock_land.xml b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml index b716c29853202..ba55f0c078d45 100644 --- a/core/res/res/layout/keyguard_screen_tab_unlock_land.xml +++ b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml @@ -135,6 +135,8 @@ android:layout_rowSpan="7" android:targetDrawables="@array/lockscreen_targets_with_camera" + android:targetDescriptions="@array/lockscreen_target_descriptions_with_camera" + android:directionDescriptions="@array/lockscreen_direction_descriptions_with_camera" android:handleDrawable="@drawable/ic_lockscreen_handle" android:waveDrawable="@drawable/ic_lockscreen_outerring" android:outerRadius="@dimen/multiwaveview_target_placement_radius" diff --git a/core/res/res/values-land/arrays.xml b/core/res/res/values-land/arrays.xml index fd492ec081fe2..57aafc8cf790d 100644 --- a/core/res/res/values-land/arrays.xml +++ b/core/res/res/values-land/arrays.xml @@ -27,13 +27,41 @@ @drawable/ic_lockscreen_soundon + + @null + @string/description_target_unlock + @null + @string/description_target_soundon + + + + @null + @string/description_direction_up + @null + @string/description_direction_down + + - @null" + @null @drawable/ic_lockscreen_unlock @null @drawable/ic_lockscreen_silent + + @null + @string/description_target_unlock + @null + @string/description_target_silent + + + + @null + @string/description_direction_up + @null + @string/description_direction_down + + @null @drawable/ic_lockscreen_unlock @@ -41,4 +69,18 @@ @drawable/ic_lockscreen_camera + + @null + @string/description_target_unlock + @null + @string/description_target_camera + + + + @null + @string/description_direction_up + @null + @string/description_direction_down + + diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml index 57e9bbfec50eb..c9043ba26889a 100644 --- a/core/res/res/values/arrays.xml +++ b/core/res/res/values/arrays.xml @@ -349,18 +349,60 @@ @null + + @string/description_target_unlock + @null + @string/description_target_soundon + @null + + + + @string/description_direction_right + @null + @string/description_direction_left + @null + + @drawable/ic_lockscreen_unlock @null @drawable/ic_lockscreen_silent - @null" + @null + + + + @string/description_target_unlock + @null + @string/description_target_silent + @null + + + + @string/description_direction_right + @null + @string/description_direction_left + @null @drawable/ic_lockscreen_unlock @null @drawable/ic_lockscreen_camera - @null" + @null + + + + @string/description_target_unlock + @null + @string/description_target_camera + @null + + + + @string/description_direction_right + @null + @string/description_direction_left + @null diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index dae9f70d4fea3..8c0d8262b7cee 100755 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -5169,6 +5169,12 @@ + + + + + + diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index fcd3bbae0f79a..6988f6bacf266 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2006,4 +2006,8 @@ + + + + diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 8e0f300072018..de10825af5c9c 100755 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -3129,6 +3129,29 @@ Enter + + + + "Sliding handle. Tap and hold." + + + "Up + + Down + + "Left + + Right + + + Unlock + + Camera + + Silent + + Sound on + Key. Headset required to hear keys while typing a password.