Skip to content

Commit 807c1a9

Browse files
sganovAndroid (Google) Code Review
authored andcommitted
Merge "Obscure speech for PIN password keys when no headset plugged in." into jb-mr1-lockscreen-dev
2 parents b8e58bb + 0cbbc57 commit 807c1a9

File tree

4 files changed

+182
-44
lines changed

4 files changed

+182
-44
lines changed

policy/src/com/android/internal/policy/impl/keyguard/KeyguardPINView.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public void onClick(View v) {
7070
}
7171
}
7272
});
73-
ok.setOnHoverListener(new NumPadKey.LiftToActivateListener(getContext()));
73+
ok.setOnHoverListener(new LiftToActivateListener(getContext()));
7474
}
7575

7676
// The delete button is of the PIN keyboard itself in some (e.g. tablet) layouts,
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright (C) 2013 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.android.internal.policy.impl.keyguard;
18+
19+
import android.content.Context;
20+
import android.view.MotionEvent;
21+
import android.view.View;
22+
import android.view.accessibility.AccessibilityManager;
23+
24+
/**
25+
* Hover listener that implements lift-to-activate interaction for
26+
* accessibility. May be added to multiple views.
27+
*/
28+
class LiftToActivateListener implements View.OnHoverListener {
29+
/** Manager used to query accessibility enabled state. */
30+
private final AccessibilityManager mAccessibilityManager;
31+
32+
private boolean mCachedClickableState;
33+
34+
public LiftToActivateListener(Context context) {
35+
mAccessibilityManager = (AccessibilityManager) context.getSystemService(
36+
Context.ACCESSIBILITY_SERVICE);
37+
}
38+
39+
@Override
40+
public boolean onHover(View v, MotionEvent event) {
41+
// When touch exploration is turned on, lifting a finger while
42+
// inside the view bounds should perform a click action.
43+
if (mAccessibilityManager.isEnabled()
44+
&& mAccessibilityManager.isTouchExplorationEnabled()) {
45+
switch (event.getActionMasked()) {
46+
case MotionEvent.ACTION_HOVER_ENTER:
47+
// Lift-to-type temporarily disables double-tap
48+
// activation by setting the view as not clickable.
49+
mCachedClickableState = v.isClickable();
50+
v.setClickable(false);
51+
break;
52+
case MotionEvent.ACTION_HOVER_EXIT:
53+
final int x = (int) event.getX();
54+
final int y = (int) event.getY();
55+
if ((x > v.getPaddingLeft()) && (y > v.getPaddingTop())
56+
&& (x < v.getWidth() - v.getPaddingRight())
57+
&& (y < v.getHeight() - v.getPaddingBottom())) {
58+
v.performClick();
59+
}
60+
v.setClickable(mCachedClickableState);
61+
break;
62+
}
63+
}
64+
65+
// Pass the event to View.onHoverEvent() to handle accessibility.
66+
v.onHoverEvent(event);
67+
68+
// Consume the event so it doesn't fall through to other views.
69+
return true;
70+
}
71+
}

policy/src/com/android/internal/policy/impl/keyguard/NumPadKey.java

Lines changed: 9 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,7 @@
2222
import android.text.style.TextAppearanceSpan;
2323
import android.util.AttributeSet;
2424
import android.view.HapticFeedbackConstants;
25-
import android.view.MotionEvent;
2625
import android.view.View;
27-
import android.view.accessibility.AccessibilityManager;
2826
import android.widget.Button;
2927
import android.widget.TextView;
3028

@@ -76,6 +74,7 @@ public NumPadKey(Context context, AttributeSet attrs, int defStyle) {
7674

7775
setOnClickListener(mListener);
7876
setOnHoverListener(new LiftToActivateListener(context));
77+
setAccessibilityDelegate(new ObscureSpeechDelegate(context));
7978

8079
mEnableHaptics = new LockPatternUtils(context).isTactileFeedbackEnabled();
8180

@@ -100,6 +99,14 @@ public NumPadKey(Context context, AttributeSet attrs, int defStyle) {
10099
setText(builder);
101100
}
102101

102+
@Override
103+
public void onDetachedFromWindow() {
104+
super.onDetachedFromWindow();
105+
106+
// Reset the "announced headset" flag when detached.
107+
ObscureSpeechDelegate.sAnnouncedHeadset = false;
108+
}
109+
103110
public void setTextView(TextView tv) {
104111
mTextView = tv;
105112
}
@@ -117,45 +124,4 @@ public void doHapticKeyClick() {
117124
| HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
118125
}
119126
}
120-
121-
/**
122-
* Hover listener that implements lift-to-activate interaction for
123-
* accessibility. May be added to multiple views.
124-
*/
125-
static class LiftToActivateListener implements View.OnHoverListener {
126-
/** Manager used to query accessibility enabled state. */
127-
private final AccessibilityManager mAccessibilityManager;
128-
129-
public LiftToActivateListener(Context context) {
130-
mAccessibilityManager = (AccessibilityManager) context.getSystemService(
131-
Context.ACCESSIBILITY_SERVICE);
132-
}
133-
134-
@Override
135-
public boolean onHover(View v, MotionEvent event) {
136-
// When touch exploration is turned on, lifting a finger while
137-
// inside the view bounds should perform a click action.
138-
if (mAccessibilityManager.isEnabled()
139-
&& mAccessibilityManager.isTouchExplorationEnabled()) {
140-
switch (event.getActionMasked()) {
141-
case MotionEvent.ACTION_HOVER_ENTER:
142-
// Lift-to-type temporarily disables double-tap
143-
// activation.
144-
v.setClickable(false);
145-
break;
146-
case MotionEvent.ACTION_HOVER_EXIT:
147-
final int x = (int) event.getX();
148-
final int y = (int) event.getY();
149-
if ((x > v.getPaddingLeft()) && (y > v.getPaddingTop())
150-
&& (x < v.getWidth() - v.getPaddingRight())
151-
&& (y < v.getHeight() - v.getPaddingBottom())) {
152-
v.performClick();
153-
}
154-
v.setClickable(true);
155-
break;
156-
}
157-
}
158-
return false;
159-
}
160-
}
161127
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Copyright (C) 2013 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.android.internal.policy.impl.keyguard;
18+
19+
import android.content.ContentResolver;
20+
import android.content.Context;
21+
import android.media.AudioManager;
22+
import android.provider.Settings;
23+
import android.view.View;
24+
import android.view.View.AccessibilityDelegate;
25+
import android.view.accessibility.AccessibilityEvent;
26+
import android.view.accessibility.AccessibilityNodeInfo;
27+
28+
import com.android.internal.R;
29+
30+
/**
31+
* Accessibility delegate that obscures speech for a view when the user has
32+
* not turned on the "speak passwords" preference and is not listening
33+
* through headphones.
34+
*/
35+
class ObscureSpeechDelegate extends AccessibilityDelegate {
36+
/** Whether any client has announced the "headset" notification. */
37+
static boolean sAnnouncedHeadset = false;
38+
39+
private final ContentResolver mContentResolver;
40+
private final AudioManager mAudioManager;
41+
42+
public ObscureSpeechDelegate(Context context) {
43+
mContentResolver = context.getContentResolver();
44+
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
45+
}
46+
47+
@Override
48+
public void sendAccessibilityEvent(View host, int eventType) {
49+
super.sendAccessibilityEvent(host, eventType);
50+
51+
// Play the "headset required" announcement the first time the user
52+
// places accessibility focus on a key.
53+
if ((eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED)
54+
&& !sAnnouncedHeadset && shouldObscureSpeech()) {
55+
sAnnouncedHeadset = true;
56+
host.announceForAccessibility(host.getContext().getString(
57+
R.string.keyboard_headset_required_to_hear_password));
58+
}
59+
}
60+
61+
@Override
62+
public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
63+
super.onPopulateAccessibilityEvent(host, event);
64+
65+
if ((event.getEventType() != AccessibilityEvent.TYPE_ANNOUNCEMENT)
66+
&& shouldObscureSpeech()) {
67+
event.getText().clear();
68+
event.setContentDescription(host.getContext().getString(
69+
R.string.keyboard_password_character_no_headset));
70+
}
71+
}
72+
73+
@Override
74+
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
75+
super.onInitializeAccessibilityNodeInfo(host, info);
76+
77+
if (shouldObscureSpeech()) {
78+
final Context ctx = host.getContext();
79+
info.setText(null);
80+
info.setContentDescription(
81+
ctx.getString(R.string.keyboard_password_character_no_headset));
82+
}
83+
}
84+
85+
@SuppressWarnings("deprecation")
86+
private boolean shouldObscureSpeech() {
87+
// The user can optionally force speaking passwords.
88+
if (Settings.Secure.getInt(mContentResolver,
89+
Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) != 0) {
90+
return false;
91+
}
92+
93+
// Always speak if the user is listening through headphones.
94+
if (mAudioManager.isWiredHeadsetOn() || mAudioManager.isBluetoothA2dpOn()) {
95+
return false;
96+
}
97+
98+
// Don't speak since this key is used to type a password.
99+
return true;
100+
}
101+
}

0 commit comments

Comments
 (0)