Skip to content

Commit 173cc7c

Browse files
sganovAndroid (Google) Code Review
authored andcommitted
Merge "Adding accessibility support to the slide lock screen"
2 parents 90200ee + f058340 commit 173cc7c

File tree

9 files changed

+312
-8
lines changed

9 files changed

+312
-8
lines changed

api/current.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,7 @@ package android {
370370
field public static final int dialogTitle = 16843250; // 0x10101f2
371371
field public static final int digits = 16843110; // 0x1010166
372372
field public static final int direction = 16843217; // 0x10101d1
373+
field public static final int directionDescriptions = 16843695; // 0x10103af
373374
field public static final int directionPriority = 16843218; // 0x10101d2
374375
field public static final int disableDependentsState = 16843249; // 0x10101f1
375376
field public static final int disabledAlpha = 16842803; // 0x1010033
@@ -938,6 +939,7 @@ package android {
938939
field public static final int tag = 16842961; // 0x10100d1
939940
field public static final int targetActivity = 16843266; // 0x1010202
940941
field public static final int targetClass = 16842799; // 0x101002f
942+
field public static final int targetDescriptions = 16843694; // 0x10103ae
941943
field public static final int targetDrawables = 16843654; // 0x1010386
942944
field public static final int targetPackage = 16842785; // 0x1010021
943945
field public static final int targetSdkVersion = 16843376; // 0x1010270

core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java

Lines changed: 186 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616

1717
package com.android.internal.widget.multiwaveview;
1818

19-
import java.util.ArrayList;
20-
2119
import android.animation.Animator;
2220
import android.animation.Animator.AnimatorListener;
2321
import android.animation.AnimatorListenerAdapter;
@@ -31,15 +29,20 @@
3129
import android.graphics.RectF;
3230
import android.graphics.drawable.Drawable;
3331
import android.os.Vibrator;
32+
import android.text.TextUtils;
3433
import android.util.AttributeSet;
3534
import android.util.Log;
3635
import android.util.TypedValue;
3736
import android.view.MotionEvent;
3837
import android.view.View;
39-
import android.view.View.MeasureSpec;
38+
import android.view.ViewConfiguration;
39+
import android.view.accessibility.AccessibilityEvent;
40+
import android.view.accessibility.AccessibilityManager;
4041

4142
import com.android.internal.R;
4243

44+
import java.util.ArrayList;
45+
4346
/**
4447
* A special widget containing a center and outer ring. Moving the center ring to the outer ring
4548
* causes an event that can be caught by implementing OnTriggerListener.
@@ -82,6 +85,8 @@ public interface OnTriggerListener {
8285
private ArrayList<TargetDrawable> mChevronDrawables = new ArrayList<TargetDrawable>();
8386
private ArrayList<Tweener> mChevronAnimations = new ArrayList<Tweener>();
8487
private ArrayList<Tweener> mTargetAnimations = new ArrayList<Tweener>();
88+
private ArrayList<String> mTargetDescriptions;
89+
private ArrayList<String> mDirectionDescriptions;
8590
private Tweener mHandleAnimation;
8691
private OnTriggerListener mOnTriggerListener;
8792
private TargetDrawable mHandleDrawable;
@@ -103,6 +108,9 @@ public interface OnTriggerListener {
103108
private boolean mDragging;
104109
private int mNewTargetResources;
105110

111+
private boolean mWaveHovered = false;
112+
private long mLastHoverExitTimeMillis = 0;
113+
106114
private AnimatorListener mResetListener = new AnimatorListenerAdapter() {
107115
public void onAnimationEnd(Animator animator) {
108116
switchToState(STATE_IDLE, mWaveCenterX, mWaveCenterY);
@@ -128,6 +136,8 @@ public void onAnimationEnd(Animator animator) {
128136
}
129137
};
130138
private int mTargetResourceId;
139+
private int mTargetDescriptionsResourceId;
140+
private int mDirectionDescriptionsResourceId;
131141

132142
public MultiWaveView(Context context) {
133143
this(context, null);
@@ -177,6 +187,25 @@ public MultiWaveView(Context context, AttributeSet attrs) {
177187
throw new IllegalStateException("Must specify at least one target drawable");
178188
}
179189

190+
// Read array of target descriptions
191+
if (a.getValue(R.styleable.MultiWaveView_targetDescriptions, outValue)) {
192+
final int resourceId = outValue.resourceId;
193+
if (resourceId == 0) {
194+
throw new IllegalStateException("Must specify target descriptions");
195+
}
196+
setTargetDescriptionsResourceId(resourceId);
197+
}
198+
199+
// Read array of direction descriptions
200+
if (a.getValue(R.styleable.MultiWaveView_directionDescriptions, outValue)) {
201+
final int resourceId = outValue.resourceId;
202+
if (resourceId == 0) {
203+
throw new IllegalStateException("Must specify direction descriptions");
204+
}
205+
setDirectionDescriptionsResourceId(resourceId);
206+
}
207+
208+
a.recycle();
180209
setVibrateEnabled(mVibrationDuration > 0);
181210
}
182211

@@ -247,6 +276,9 @@ private void switchToState(int state, float x, float y) {
247276
showTargets(true);
248277
mHandleDrawable.setState(TargetDrawable.STATE_ACTIVE);
249278
setGrabbedState(OnTriggerListener.CENTER_HANDLE);
279+
if (AccessibilityManager.getInstance(mContext).isEnabled()) {
280+
announceTargets();
281+
}
250282
break;
251283

252284
case STATE_TRACKING:
@@ -347,6 +379,13 @@ private void dispatchTriggerEvent(int whichHandle) {
347379
}
348380
}
349381

382+
private void dispatchGrabbedEvent(int whichHandler) {
383+
vibrate();
384+
if (mOnTriggerListener != null) {
385+
mOnTriggerListener.onGrabbed(this, whichHandler);
386+
}
387+
}
388+
350389
private void doFinish() {
351390
final int activeTarget = mActiveTarget;
352391
boolean targetHit = activeTarget != -1;
@@ -475,6 +514,7 @@ private void internalSetTargetResources(int resourceId) {
475514
Drawable drawable = array.getDrawable(i);
476515
targetDrawables.add(new TargetDrawable(res, drawable));
477516
}
517+
array.recycle();
478518
mTargetResourceId = resourceId;
479519
mTargetDrawables = targetDrawables;
480520
updateTargetPositions();
@@ -498,6 +538,48 @@ public int getTargetResourceId() {
498538
return mTargetResourceId;
499539
}
500540

541+
/**
542+
* Sets the resource id specifying the target descriptions for accessibility.
543+
*
544+
* @param resourceId The resource id.
545+
*/
546+
public void setTargetDescriptionsResourceId(int resourceId) {
547+
mTargetDescriptionsResourceId = resourceId;
548+
if (mTargetDescriptions != null) {
549+
mTargetDescriptions.clear();
550+
}
551+
}
552+
553+
/**
554+
* Gets the resource id specifying the target descriptions for accessibility.
555+
*
556+
* @return The resource id.
557+
*/
558+
public int getTargetDescriptionsResourceId() {
559+
return mTargetDescriptionsResourceId;
560+
}
561+
562+
/**
563+
* Sets the resource id specifying the target direction descriptions for accessibility.
564+
*
565+
* @param resourceId The resource id.
566+
*/
567+
public void setDirectionDescriptionsResourceId(int resourceId) {
568+
mDirectionDescriptionsResourceId = resourceId;
569+
if (mDirectionDescriptions != null) {
570+
mDirectionDescriptions.clear();
571+
}
572+
}
573+
574+
/**
575+
* Gets the resource id specifying the target direction descriptions.
576+
*
577+
* @return The resource id.
578+
*/
579+
public int getDirectionDescriptionsResourceId() {
580+
return mDirectionDescriptionsResourceId;
581+
}
582+
501583
/**
502584
* Enable or disable vibrate on touch.
503585
*
@@ -593,6 +675,43 @@ private void handleDown(MotionEvent event) {
593675
}
594676
}
595677

678+
@Override
679+
public boolean onHoverEvent(MotionEvent event) {
680+
if (AccessibilityManager.getInstance(mContext).isTouchExplorationEnabled()) {
681+
final int action = event.getAction();
682+
switch (action) {
683+
case MotionEvent.ACTION_HOVER_ENTER:
684+
case MotionEvent.ACTION_HOVER_MOVE:
685+
final float dx = event.getX() - mWaveCenterX;
686+
final float dy = event.getY() - mWaveCenterY;
687+
if (dist2(dx,dy) <= square(mTapRadius)) {
688+
if (!mWaveHovered) {
689+
mWaveHovered = true;
690+
final long timeSinceLastHoverExitMillis =
691+
event.getEventTime() - mLastHoverExitTimeMillis;
692+
final long recurringEventsInterval =
693+
ViewConfiguration.getSendRecurringAccessibilityEventsInterval();
694+
if (timeSinceLastHoverExitMillis > recurringEventsInterval) {
695+
String text =
696+
mContext.getString(R.string.content_description_sliding_handle);
697+
announceText(text);
698+
}
699+
}
700+
} else {
701+
mWaveHovered = false;
702+
}
703+
break;
704+
case MotionEvent.ACTION_HOVER_EXIT:
705+
mLastHoverExitTimeMillis = event.getEventTime();
706+
mWaveHovered = false;
707+
break;
708+
default:
709+
mWaveHovered = false;
710+
}
711+
}
712+
return super.onHoverEvent(event);
713+
}
714+
596715
private void handleUp(MotionEvent event) {
597716
if (DEBUG && mDragging) Log.v(TAG, "** Handle RELEASE");
598717
switchToState(STATE_FINISH, event.getX(), event.getY());
@@ -663,7 +782,11 @@ private void handleMove(MotionEvent event) {
663782
invalidateGlobalRegion(mHandleDrawable);
664783

665784
if (mActiveTarget != activeTarget && activeTarget != -1) {
666-
vibrate();
785+
dispatchGrabbedEvent(activeTarget);
786+
if (AccessibilityManager.getInstance(mContext).isEnabled()) {
787+
String targetContentDescription = getTargetDescription(activeTarget);
788+
announceText(targetContentDescription);
789+
}
667790
}
668791
mActiveTarget = activeTarget;
669792
}
@@ -771,4 +894,62 @@ private float dist2(float dx, float dy) {
771894
return dx*dx + dy*dy;
772895
}
773896

774-
}
897+
private void announceTargets() {
898+
StringBuilder utterance = new StringBuilder();
899+
final int targetCount = mTargetDrawables.size();
900+
for (int i = 0; i < targetCount; i++) {
901+
String targetDescription = getTargetDescription(i);
902+
String directionDescription = getDirectionDescription(i);
903+
if (!TextUtils.isEmpty(targetDescription)
904+
&& !TextUtils.isEmpty(directionDescription)) {
905+
utterance.append(targetDescription);
906+
utterance.append(" ");
907+
utterance.append(directionDescription);
908+
utterance.append(".");
909+
}
910+
}
911+
announceText(utterance.toString());
912+
}
913+
914+
private void announceText(String text) {
915+
setContentDescription(text);
916+
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
917+
setContentDescription(null);
918+
}
919+
920+
private String getTargetDescription(int index) {
921+
if (mTargetDescriptions == null || mTargetDescriptions.isEmpty()) {
922+
mTargetDescriptions = loadDescriptions(mTargetDescriptionsResourceId);
923+
if (mTargetDrawables.size() != mTargetDescriptions.size()) {
924+
Log.w(TAG, "The number of target drawables must be"
925+
+ " euqal to the number of target descriptions.");
926+
return null;
927+
}
928+
}
929+
return mTargetDescriptions.get(index);
930+
}
931+
932+
private String getDirectionDescription(int index) {
933+
if (mDirectionDescriptions == null || mDirectionDescriptions.isEmpty()) {
934+
mDirectionDescriptions = loadDescriptions(mDirectionDescriptionsResourceId);
935+
if (mTargetDrawables.size() != mDirectionDescriptions.size()) {
936+
Log.w(TAG, "The number of target drawables must be"
937+
+ " euqal to the number of direction descriptions.");
938+
return null;
939+
}
940+
}
941+
return mDirectionDescriptions.get(index);
942+
}
943+
944+
private ArrayList<String> loadDescriptions(int resourceId) {
945+
TypedArray array = getContext().getResources().obtainTypedArray(resourceId);
946+
final int count = array.length();
947+
ArrayList<String> targetContentDescriptions = new ArrayList<String>(count);
948+
for (int i = 0; i < count; i++) {
949+
String contentDescription = array.getString(i);
950+
targetContentDescriptions.add(contentDescription);
951+
}
952+
array.recycle();
953+
return targetContentDescriptions;
954+
}
955+
}

core/res/res/layout/keyguard_screen_tab_unlock.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@
130130
android:layout_alignParentBottom="true"
131131

132132
android:targetDrawables="@array/lockscreen_targets_with_camera"
133+
android:targetDescriptions="@array/lockscreen_target_descriptions_with_camera"
134+
android:directionDescriptions="@array/lockscreen_direction_descriptions_with_camera"
133135
android:handleDrawable="@drawable/ic_lockscreen_handle"
134136
android:waveDrawable="@drawable/ic_lockscreen_outerring"
135137
android:outerRadius="@dimen/multiwaveview_target_placement_radius"

core/res/res/layout/keyguard_screen_tab_unlock_land.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@
135135
android:layout_rowSpan="7"
136136

137137
android:targetDrawables="@array/lockscreen_targets_with_camera"
138+
android:targetDescriptions="@array/lockscreen_target_descriptions_with_camera"
139+
android:directionDescriptions="@array/lockscreen_direction_descriptions_with_camera"
138140
android:handleDrawable="@drawable/ic_lockscreen_handle"
139141
android:waveDrawable="@drawable/ic_lockscreen_outerring"
140142
android:outerRadius="@dimen/multiwaveview_target_placement_radius"

core/res/res/values-land/arrays.xml

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,60 @@
2727
<item>@drawable/ic_lockscreen_soundon</item>
2828
</array>
2929

30+
<array name="lockscreen_target_descriptions_when_silent">
31+
<item>@null</item>
32+
<item>@string/description_target_unlock</item>
33+
<item>@null</item>
34+
<item>@string/description_target_soundon</item>
35+
</array>
36+
37+
<array name="lockscreen_direction_descriptions_when_silent">
38+
<item>@null</item>
39+
<item>@string/description_direction_up</item>
40+
<item>@null</item>
41+
<item>@string/description_direction_down</item>
42+
</array>
43+
3044
<array name="lockscreen_targets_when_soundon">
31-
<item>@null</item>"
45+
<item>@null</item>
3246
<item>@drawable/ic_lockscreen_unlock</item>
3347
<item>@null</item>
3448
<item>@drawable/ic_lockscreen_silent</item>
3549
</array>
3650

51+
<array name="lockscreen_target_descriptions_when_soundon">
52+
<item>@null</item>
53+
<item>@string/description_target_unlock</item>
54+
<item>@null</item>
55+
<item>@string/description_target_silent</item>
56+
</array>
57+
58+
<array name="lockscreen_direction_descriptions_when_soundon">
59+
<item>@null</item>
60+
<item>@string/description_direction_up</item>
61+
<item>@null</item>
62+
<item>@string/description_direction_down</item>
63+
</array>
64+
3765
<array name="lockscreen_targets_with_camera">
3866
<item>@null</item>
3967
<item>@drawable/ic_lockscreen_unlock</item>
4068
<item>@null</item>
4169
<item>@drawable/ic_lockscreen_camera</item>
4270
</array>
4371

72+
<array name="lockscreen_target_descriptions_with_camera">
73+
<item>@null</item>
74+
<item>@string/description_target_unlock</item>
75+
<item>@null</item>
76+
<item>@string/description_target_camera</item>
77+
</array>
78+
79+
<array name="lockscreen_direction_descriptions_with_camera">
80+
<item>@null</item>
81+
<item>@string/description_direction_up</item>
82+
<item>@null</item>
83+
<item>@string/description_direction_down</item>
84+
</array>
85+
4486
</resources>

0 commit comments

Comments
 (0)