Mercurial > hg > ccmiandroid
view 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 |
line wrap: on
line source
/* CCmI Diagram Editor for Android Copyright (C) 2012 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package uk.ac.qmul.eecs.ccmi.accessibility; import uk.ac.qmul.eecs.ccmi.utilities.ILogger; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.Spinner; import android.widget.TextView; /** * The {@code LayoutSonifier} provides the sonification of a {@code View}. When a {@code View} is sonified * the user can swipe on it with their finger and receive audio and haptic cues about it. * * The {@code View} is rendered in audio by uttering (through the {@code Android} test to speech synthesizer) the content * description, as per {@code getContentDescription()}. Furthermore, if the {@code View} is also a {@code TextView} * the text is uttered too, right after the content description. Haptic rendering is just a vibration of the device. * * A {@code View} that is also a {@code ViewGroup} is recursively searched in its children until the non {@code ViewGruop} * that is under the coordinates of the user touch is found. * * The way each {@code View} is sonified can be customized by subclassing this class and providing a custom * implementation of {@code SonifyView()}. * */ public class LayoutSonifier { private static LayoutSonifier singleton; /* in order not to utter the view's */ private View lastTouchedView; /** * static factory method enforces the existance of only one instance of this class at time (singleton) * * @return an instance of {@code LayoutSonifier} */ public static LayoutSonifier getInstance(){ if(singleton == null) singleton = new LayoutSonifier(); return singleton; } /* privatize the constructor to enforce singleton */ protected LayoutSonifier(){} public boolean onTouch(View view, MotionEvent evt, AccessibilityService accessibilityService) { if(accessibilityService == null) throw new IllegalArgumentException("accessibilityService cannot be null"); switch(evt.getAction()){ case MotionEvent.ACTION_MOVE : { if(evt.getPointerCount() > 1) // double finger is for scrolling return false; View newView = findView((ViewGroup)view,evt.getX(),evt.getY(),view.getLeft(),view.getTop()); if(newView != lastTouchedView){ sonifyView(newView,accessibilityService); lastTouchedView = newView; return false; }else{ return true; } } case MotionEvent.ACTION_UP : { lastTouchedView = null; return false; } default : return false; } } /** * This method is called when the view under the user's finger is found. The content * description (as per {@code View.getContentDesctpion()} is uttered if not null, otherwise, * if the View is a text view, the string returned by {@code TextView.getText()} is uttered. * * A vibration is triggered regardless of the type of the View. * * Subclasses can overwrite this method to provide their own sonification of the view touched * by the user. Unless it's an {@code AccessibleSpinner} or a {@code AccessibleCheckbox} * is never a {@code ViewGroup} as if a {@code ViewGroup} is met the view it contains * are recursively searched for the one under the user's finger. * * @param v the view touched by the user * @param accessibilityService the accessibility service used to sonify the view */ protected void sonifyView(View v, AccessibilityService accessibilityService){ if(v == null) return; if(v instanceof TextView ){ accessibilityService.vibrate(); CharSequence contentDescription = v.getContentDescription(); /* if the view has accessibility content description set, use it */ if(contentDescription != null ){ accessibilityService.speak(contentDescription.toString() + ((v instanceof TextView) ? ((TextView)v).getText() : "") ); ILogger.logHover(contentDescription.toString()); }else{ accessibilityService.speak(((TextView)v).getText().toString()); ILogger.logHover(((TextView)v).getText().toString()); } }else if(v instanceof AccessibleCheckbox){ AccessibleCheckbox ab = (AccessibleCheckbox)v; boolean isChecked = ab.getChecks()[ab.getSelectedValuePosition()]; accessibilityService.vibrate(); accessibilityService.speak(ab.getSelectedValue()+ (isChecked ? ", checked" : ", unchecked")); ILogger.logHover("check box"); }else if (v instanceof Spinner){ accessibilityService.vibrate(); accessibilityService.speak(v.getContentDescription()+" "+((Spinner)v).getSelectedItem()); ILogger.logHover("spinner"); } } private static View findView(ViewGroup viewGroup, float x, float y, float offsetX, float offsetY){ x-= offsetX; y-= offsetY; /* look for other views which one is under the finger of the user */ for(int i=0; i<viewGroup.getChildCount(); i++){ View v = viewGroup.getChildAt(i); if(y > v.getTop() && y < (v.getTop()+v.getHeight()) && x > v.getLeft() && x < (v.getLeft()+v.getWidth()) ){ if(v instanceof ViewGroup){ if(v instanceof AccessibleSpinner || v instanceof AccessibleCheckbox) return v; return findView((ViewGroup)v,x,y,v.getLeft(),v.getTop()); }else{ return v; } } } return null; } }