annotate src/uk/ac/qmul/eecs/ccmi/accessibility/LayoutSonifier.java @ 1:66b3a838feca logging tip

Added logging of user interaction
author Fiore Martin <fiore@eecs.qmul.ac.uk>
date Tue, 12 Feb 2013 15:31:48 +0000
parents e0ee6ac3a45f
children
rev   line source
fiore@0 1 /*
fiore@0 2 CCmI Diagram Editor for Android
fiore@0 3
fiore@0 4 Copyright (C) 2012 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
fiore@0 5
fiore@0 6 This program is free software: you can redistribute it and/or modify
fiore@0 7 it under the terms of the GNU General Public License as published by
fiore@0 8 the Free Software Foundation, either version 3 of the License, or
fiore@0 9 (at your option) any later version.
fiore@0 10
fiore@0 11 This program is distributed in the hope that it will be useful,
fiore@0 12 but WITHOUT ANY WARRANTY; without even the implied warranty of
fiore@0 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
fiore@0 14 GNU General Public License for more details.
fiore@0 15
fiore@0 16 You should have received a copy of the GNU General Public License
fiore@0 17 along with this program. If not, see <http://www.gnu.org/licenses/>.
fiore@0 18 */
fiore@0 19 package uk.ac.qmul.eecs.ccmi.accessibility;
fiore@0 20
fiore@1 21 import uk.ac.qmul.eecs.ccmi.utilities.ILogger;
fiore@0 22 import android.view.MotionEvent;
fiore@0 23 import android.view.View;
fiore@0 24 import android.view.ViewGroup;
fiore@0 25 import android.widget.Spinner;
fiore@0 26 import android.widget.TextView;
fiore@0 27
fiore@0 28 /**
fiore@0 29 * The {@code LayoutSonifier} provides the sonification of a {@code View}. When a {@code View} is sonified
fiore@0 30 * the user can swipe on it with their finger and receive audio and haptic cues about it.
fiore@0 31 *
fiore@0 32 * The {@code View} is rendered in audio by uttering (through the {@code Android} test to speech synthesizer) the content
fiore@0 33 * description, as per {@code getContentDescription()}. Furthermore, if the {@code View} is also a {@code TextView}
fiore@0 34 * the text is uttered too, right after the content description. Haptic rendering is just a vibration of the device.
fiore@0 35 *
fiore@0 36 * A {@code View} that is also a {@code ViewGroup} is recursively searched in its children until the non {@code ViewGruop}
fiore@0 37 * that is under the coordinates of the user touch is found.
fiore@0 38 *
fiore@0 39 * The way each {@code View} is sonified can be customized by subclassing this class and providing a custom
fiore@0 40 * implementation of {@code SonifyView()}.
fiore@0 41 *
fiore@0 42 */
fiore@0 43 public class LayoutSonifier {
fiore@0 44 private static LayoutSonifier singleton;
fiore@0 45 /* in order not to utter the view's */
fiore@0 46 private View lastTouchedView;
fiore@0 47
fiore@0 48 /**
fiore@0 49 * static factory method enforces the existance of only one instance of this class at time (singleton)
fiore@0 50 *
fiore@0 51 * @return an instance of {@code LayoutSonifier}
fiore@0 52 */
fiore@0 53 public static LayoutSonifier getInstance(){
fiore@0 54 if(singleton == null)
fiore@0 55 singleton = new LayoutSonifier();
fiore@0 56 return singleton;
fiore@0 57 }
fiore@0 58
fiore@0 59 /* privatize the constructor to enforce singleton */
fiore@0 60 protected LayoutSonifier(){}
fiore@0 61
fiore@0 62 public boolean onTouch(View view, MotionEvent evt, AccessibilityService accessibilityService) {
fiore@0 63 if(accessibilityService == null)
fiore@0 64 throw new IllegalArgumentException("accessibilityService cannot be null");
fiore@0 65 switch(evt.getAction()){
fiore@0 66 case MotionEvent.ACTION_MOVE : {
fiore@0 67 if(evt.getPointerCount() > 1) // double finger is for scrolling
fiore@0 68 return false;
fiore@0 69 View newView = findView((ViewGroup)view,evt.getX(),evt.getY(),view.getLeft(),view.getTop());
fiore@0 70 if(newView != lastTouchedView){
fiore@0 71 sonifyView(newView,accessibilityService);
fiore@0 72 lastTouchedView = newView;
fiore@0 73 return false;
fiore@0 74 }else{
fiore@0 75 return true;
fiore@0 76 }
fiore@0 77 }
fiore@0 78 case MotionEvent.ACTION_UP : {
fiore@0 79 lastTouchedView = null;
fiore@0 80 return false;
fiore@0 81 }
fiore@0 82 default : return false;
fiore@0 83 }
fiore@0 84 }
fiore@0 85
fiore@0 86 /**
fiore@0 87 * This method is called when the view under the user's finger is found. The content
fiore@0 88 * description (as per {@code View.getContentDesctpion()} is uttered if not null, otherwise,
fiore@0 89 * if the View is a text view, the string returned by {@code TextView.getText()} is uttered.
fiore@0 90 *
fiore@0 91 * A vibration is triggered regardless of the type of the View.
fiore@0 92 *
fiore@0 93 * Subclasses can overwrite this method to provide their own sonification of the view touched
fiore@0 94 * by the user. Unless it's an {@code AccessibleSpinner} or a {@code AccessibleCheckbox}
fiore@0 95 * is never a {@code ViewGroup} as if a {@code ViewGroup} is met the view it contains
fiore@0 96 * are recursively searched for the one under the user's finger.
fiore@0 97 *
fiore@0 98 * @param v the view touched by the user
fiore@0 99 * @param accessibilityService the accessibility service used to sonify the view
fiore@0 100 */
fiore@0 101 protected void sonifyView(View v, AccessibilityService accessibilityService){
fiore@0 102 if(v == null)
fiore@0 103 return;
fiore@0 104 if(v instanceof TextView ){
fiore@0 105 accessibilityService.vibrate();
fiore@0 106 CharSequence contentDescription = v.getContentDescription();
fiore@0 107 /* if the view has accessibility content description set, use it */
fiore@1 108 if(contentDescription != null ){
fiore@0 109 accessibilityService.speak(contentDescription.toString() +
fiore@0 110 ((v instanceof TextView) ? ((TextView)v).getText() : "") );
fiore@1 111 ILogger.logHover(contentDescription.toString());
fiore@1 112 }else{
fiore@0 113 accessibilityService.speak(((TextView)v).getText().toString());
fiore@1 114 ILogger.logHover(((TextView)v).getText().toString());
fiore@1 115 }
fiore@0 116 }else if(v instanceof AccessibleCheckbox){
fiore@0 117 AccessibleCheckbox ab = (AccessibleCheckbox)v;
fiore@0 118 boolean isChecked = ab.getChecks()[ab.getSelectedValuePosition()];
fiore@0 119 accessibilityService.vibrate();
fiore@0 120 accessibilityService.speak(ab.getSelectedValue()+ (isChecked ? ", checked" : ", unchecked"));
fiore@1 121 ILogger.logHover("check box");
fiore@0 122 }else if (v instanceof Spinner){
fiore@0 123 accessibilityService.vibrate();
fiore@0 124 accessibilityService.speak(v.getContentDescription()+" "+((Spinner)v).getSelectedItem());
fiore@1 125 ILogger.logHover("spinner");
fiore@0 126 }
fiore@0 127 }
fiore@0 128
fiore@0 129 private static View findView(ViewGroup viewGroup, float x, float y, float offsetX, float offsetY){
fiore@0 130 x-= offsetX;
fiore@0 131 y-= offsetY;
fiore@0 132
fiore@0 133 /* look for other views which one is under the finger of the user */
fiore@0 134 for(int i=0; i<viewGroup.getChildCount(); i++){
fiore@0 135 View v = viewGroup.getChildAt(i);
fiore@0 136 if(y > v.getTop() && y < (v.getTop()+v.getHeight()) &&
fiore@0 137 x > v.getLeft() && x < (v.getLeft()+v.getWidth()) ){
fiore@0 138 if(v instanceof ViewGroup){
fiore@0 139 if(v instanceof AccessibleSpinner || v instanceof AccessibleCheckbox)
fiore@0 140 return v;
fiore@0 141 return findView((ViewGroup)v,x,y,v.getLeft(),v.getTop());
fiore@0 142 }else{
fiore@0 143 return v;
fiore@0 144 }
fiore@0 145 }
fiore@0 146 }
fiore@0 147 return null;
fiore@0 148 }
fiore@0 149 }
fiore@0 150