Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 807c1a99 authored by Svetoslav Ganov's avatar Svetoslav Ganov Committed by Android (Google) Code Review
Browse files

Merge "Obscure speech for PIN password keys when no headset plugged in." into jb-mr1-lockscreen-dev

parents b8e58bb6 0cbbc576
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -70,7 +70,7 @@ public class KeyguardPINView extends KeyguardAbsKeyInputView
                    }
                }
            });
            ok.setOnHoverListener(new NumPadKey.LiftToActivateListener(getContext()));
            ok.setOnHoverListener(new LiftToActivateListener(getContext()));
        }

        // The delete button is of the PIN keyboard itself in some (e.g. tablet) layouts,
+71 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2013 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.policy.impl.keyguard;

import android.content.Context;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityManager;

/**
 * Hover listener that implements lift-to-activate interaction for
 * accessibility. May be added to multiple views.
 */
class LiftToActivateListener implements View.OnHoverListener {
    /** Manager used to query accessibility enabled state. */
    private final AccessibilityManager mAccessibilityManager;

    private boolean mCachedClickableState;

    public LiftToActivateListener(Context context) {
        mAccessibilityManager = (AccessibilityManager) context.getSystemService(
                Context.ACCESSIBILITY_SERVICE);
    }

    @Override
    public boolean onHover(View v, MotionEvent event) {
        // When touch exploration is turned on, lifting a finger while
        // inside the view bounds should perform a click action.
        if (mAccessibilityManager.isEnabled()
                && mAccessibilityManager.isTouchExplorationEnabled()) {
            switch (event.getActionMasked()) {
                case MotionEvent.ACTION_HOVER_ENTER:
                    // Lift-to-type temporarily disables double-tap
                    // activation by setting the view as not clickable.
                    mCachedClickableState = v.isClickable();
                    v.setClickable(false);
                    break;
                case MotionEvent.ACTION_HOVER_EXIT:
                    final int x = (int) event.getX();
                    final int y = (int) event.getY();
                    if ((x > v.getPaddingLeft()) && (y > v.getPaddingTop())
                            && (x < v.getWidth() - v.getPaddingRight())
                            && (y < v.getHeight() - v.getPaddingBottom())) {
                        v.performClick();
                    }
                    v.setClickable(mCachedClickableState);
                    break;
            }
        }

        // Pass the event to View.onHoverEvent() to handle accessibility.
        v.onHoverEvent(event);

        // Consume the event so it doesn't fall through to other views.
        return true;
    }
}
 No newline at end of file
+9 −43
Original line number Diff line number Diff line
@@ -22,9 +22,7 @@ import android.text.SpannableStringBuilder;
import android.text.style.TextAppearanceSpan;
import android.util.AttributeSet;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
import android.widget.Button;
import android.widget.TextView;

@@ -76,6 +74,7 @@ public class NumPadKey extends Button {

        setOnClickListener(mListener);
        setOnHoverListener(new LiftToActivateListener(context));
        setAccessibilityDelegate(new ObscureSpeechDelegate(context));

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

@@ -100,6 +99,14 @@ public class NumPadKey extends Button {
        setText(builder);
    }

    @Override
    public void onDetachedFromWindow() {
        super.onDetachedFromWindow();

        // Reset the "announced headset" flag when detached.
        ObscureSpeechDelegate.sAnnouncedHeadset = false;
    }

    public void setTextView(TextView tv) {
        mTextView = tv;
    }
@@ -117,45 +124,4 @@ public class NumPadKey extends Button {
                    | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
        }
    }

    /**
     * Hover listener that implements lift-to-activate interaction for
     * accessibility. May be added to multiple views.
     */
    static class LiftToActivateListener implements View.OnHoverListener {
        /** Manager used to query accessibility enabled state. */
        private final AccessibilityManager mAccessibilityManager;

        public LiftToActivateListener(Context context) {
            mAccessibilityManager = (AccessibilityManager) context.getSystemService(
                    Context.ACCESSIBILITY_SERVICE);
        }

        @Override
        public boolean onHover(View v, MotionEvent event) {
            // When touch exploration is turned on, lifting a finger while
            // inside the view bounds should perform a click action.
            if (mAccessibilityManager.isEnabled()
                    && mAccessibilityManager.isTouchExplorationEnabled()) {
                switch (event.getActionMasked()) {
                    case MotionEvent.ACTION_HOVER_ENTER:
                        // Lift-to-type temporarily disables double-tap
                        // activation.
                        v.setClickable(false);
                        break;
                    case MotionEvent.ACTION_HOVER_EXIT:
                        final int x = (int) event.getX();
                        final int y = (int) event.getY();
                        if ((x > v.getPaddingLeft()) && (y > v.getPaddingTop())
                                && (x < v.getWidth() - v.getPaddingRight())
                                && (y < v.getHeight() - v.getPaddingBottom())) {
                            v.performClick();
                        }
                        v.setClickable(true);
                        break;
                }
            }
            return false;
        }
    }
}
+101 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2013 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.policy.impl.keyguard;

import android.content.ContentResolver;
import android.content.Context;
import android.media.AudioManager;
import android.provider.Settings;
import android.view.View;
import android.view.View.AccessibilityDelegate;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;

import com.android.internal.R;

/**
 * Accessibility delegate that obscures speech for a view when the user has
 * not turned on the "speak passwords" preference and is not listening
 * through headphones.
 */
class ObscureSpeechDelegate extends AccessibilityDelegate {
    /** Whether any client has announced the "headset" notification. */
    static boolean sAnnouncedHeadset = false;

    private final ContentResolver mContentResolver;
    private final AudioManager mAudioManager;

    public ObscureSpeechDelegate(Context context) {
        mContentResolver = context.getContentResolver();
        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
    }

    @Override
    public void sendAccessibilityEvent(View host, int eventType) {
        super.sendAccessibilityEvent(host, eventType);

        // Play the "headset required" announcement the first time the user
        // places accessibility focus on a key.
        if ((eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED)
                && !sAnnouncedHeadset && shouldObscureSpeech()) {
            sAnnouncedHeadset = true;
            host.announceForAccessibility(host.getContext().getString(
                    R.string.keyboard_headset_required_to_hear_password));
        }
    }

    @Override
    public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
        super.onPopulateAccessibilityEvent(host, event);

        if ((event.getEventType() != AccessibilityEvent.TYPE_ANNOUNCEMENT)
                && shouldObscureSpeech()) {
            event.getText().clear();
            event.setContentDescription(host.getContext().getString(
                    R.string.keyboard_password_character_no_headset));
        }
    }

    @Override
    public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
        super.onInitializeAccessibilityNodeInfo(host, info);

        if (shouldObscureSpeech()) {
            final Context ctx = host.getContext();
            info.setText(null);
            info.setContentDescription(
                    ctx.getString(R.string.keyboard_password_character_no_headset));
        }
    }

    @SuppressWarnings("deprecation")
    private boolean shouldObscureSpeech() {
        // The user can optionally force speaking passwords.
        if (Settings.Secure.getInt(mContentResolver,
                Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) != 0) {
            return false;
        }

        // Always speak if the user is listening through headphones.
        if (mAudioManager.isWiredHeadsetOn() || mAudioManager.isBluetoothA2dpOn()) {
            return false;
        }

        // Don't speak since this key is used to type a password.
        return true;
    }
}
 No newline at end of file