10cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv/* 20cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv * Copyright (C) 2013 The Android Open Source Project 30cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv * 40cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv * Licensed under the Apache License, Version 2.0 (the "License"); 50cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv * you may not use this file except in compliance with the License. 60cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv * You may obtain a copy of the License at 70cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv * 80cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv * http://www.apache.org/licenses/LICENSE-2.0 90cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv * 100cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv * Unless required by applicable law or agreed to in writing, software 110cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv * distributed under the License is distributed on an "AS IS" BASIS, 120cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 130cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv * See the License for the specific language governing permissions and 140cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv * limitations under the License. 150cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv */ 160cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv 175ecd81154fa039961f65bb4e36d18ac555b0d1d6Jim Millerpackage com.android.keyguard; 180cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv 190cbbc5766fb7c56c49ec55eece905fa7764a8b2balanvimport android.content.ContentResolver; 200cbbc5766fb7c56c49ec55eece905fa7764a8b2balanvimport android.content.Context; 210cbbc5766fb7c56c49ec55eece905fa7764a8b2balanvimport android.media.AudioManager; 22b6e0cb97b7d8e4acf345efc8066f1587408b83e0Alan Viveretteimport android.os.UserHandle; 230cbbc5766fb7c56c49ec55eece905fa7764a8b2balanvimport android.provider.Settings; 240cbbc5766fb7c56c49ec55eece905fa7764a8b2balanvimport android.view.View; 250cbbc5766fb7c56c49ec55eece905fa7764a8b2balanvimport android.view.View.AccessibilityDelegate; 260cbbc5766fb7c56c49ec55eece905fa7764a8b2balanvimport android.view.accessibility.AccessibilityEvent; 270cbbc5766fb7c56c49ec55eece905fa7764a8b2balanvimport android.view.accessibility.AccessibilityNodeInfo; 280cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv 290cbbc5766fb7c56c49ec55eece905fa7764a8b2balanvimport com.android.internal.R; 300cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv 310cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv/** 320cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv * Accessibility delegate that obscures speech for a view when the user has 330cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv * not turned on the "speak passwords" preference and is not listening 340cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv * through headphones. 350cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv */ 360cbbc5766fb7c56c49ec55eece905fa7764a8b2balanvclass ObscureSpeechDelegate extends AccessibilityDelegate { 370cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv /** Whether any client has announced the "headset" notification. */ 380cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv static boolean sAnnouncedHeadset = false; 390cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv 400cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv private final ContentResolver mContentResolver; 410cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv private final AudioManager mAudioManager; 420cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv 430cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv public ObscureSpeechDelegate(Context context) { 440cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv mContentResolver = context.getContentResolver(); 450cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 460cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv } 470cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv 480cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv @Override 490cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv public void sendAccessibilityEvent(View host, int eventType) { 500cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv super.sendAccessibilityEvent(host, eventType); 510cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv 520cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv // Play the "headset required" announcement the first time the user 530cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv // places accessibility focus on a key. 540cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv if ((eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) 550cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv && !sAnnouncedHeadset && shouldObscureSpeech()) { 560cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv sAnnouncedHeadset = true; 570cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv host.announceForAccessibility(host.getContext().getString( 580cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv R.string.keyboard_headset_required_to_hear_password)); 590cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv } 600cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv } 610cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv 620cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv @Override 630cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) { 640cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv super.onPopulateAccessibilityEvent(host, event); 650cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv 660cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv if ((event.getEventType() != AccessibilityEvent.TYPE_ANNOUNCEMENT) 670cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv && shouldObscureSpeech()) { 680cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv event.getText().clear(); 690cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv event.setContentDescription(host.getContext().getString( 700cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv R.string.keyboard_password_character_no_headset)); 710cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv } 720cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv } 730cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv 740cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv @Override 750cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { 760cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv super.onInitializeAccessibilityNodeInfo(host, info); 770cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv 780cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv if (shouldObscureSpeech()) { 790cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv final Context ctx = host.getContext(); 800cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv info.setText(null); 810cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv info.setContentDescription( 820cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv ctx.getString(R.string.keyboard_password_character_no_headset)); 830cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv } 840cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv } 850cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv 860cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv @SuppressWarnings("deprecation") 870cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv private boolean shouldObscureSpeech() { 880cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv // The user can optionally force speaking passwords. 89b6e0cb97b7d8e4acf345efc8066f1587408b83e0Alan Viverette if (Settings.Secure.getIntForUser(mContentResolver, 90b6e0cb97b7d8e4acf345efc8066f1587408b83e0Alan Viverette Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0, UserHandle.USER_CURRENT) != 0) { 910cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv return false; 920cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv } 930cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv 940cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv // Always speak if the user is listening through headphones. 950cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv if (mAudioManager.isWiredHeadsetOn() || mAudioManager.isBluetoothA2dpOn()) { 960cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv return false; 970cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv } 980cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv 990cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv // Don't speak since this key is used to type a password. 1000cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv return true; 1010cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv } 1020cbbc5766fb7c56c49ec55eece905fa7764a8b2balanv}