<commit_before>package info.spielproject.spiel
package presenters

import collection.JavaConversions._

import android.app.{ActivityManager, Service}
import android.content.Context
import android.os.Build.VERSION
import android.os.SystemClock
import android.util.Log
import android.view.accessibility.{AccessibilityEvent, AccessibilityNodeInfo}
import AccessibilityEvent._
import AccessibilityNodeInfo._

import routing._

/**
 * Represents code that is called when a specific <code>AccessibilityEvent</code> is received.
*/

abstract class Callback {

  /**
   * Called with an <code>AccessibilityEvent</code>.
   * @return <code>true</code> if event processing should stop, false otherwise
  */

  def apply(e:AccessibilityEvent):Boolean

}

/**
 * Represents a callback that is written in native Scala code.
*/

class NativeCallback(f:AccessibilityEvent => Boolean) extends Callback {
  def apply(e:AccessibilityEvent) = f(e)
}

/**
 * Singleton that stores the 50 most recent <code>AccessibilityEvent</code> objects for review.
*/

object EventReviewQueue extends collection.mutable.Queue[AccessibilityEvent] {

  /**
   * Adds an event to the queue, stripping excess items if necessary.
  */

  def apply(e:AccessibilityEvent) = {
    enqueue(e)
    while(length > 50) dequeue()
  }

}

case class EventPayload(event:AccessibilityEvent, eventType:Int)

/**
 * Maps a given <code>Callback</code> to events originating from a given 
 * package and class.
 *
 * Passing a blank string for either indicates events from all packages or all classes.
*/

class Presenter(directive:Option[HandlerDirective] = None) extends Handler[EventPayload](Presenter, directive) {

  def this(pkg:String, cls:String) = this(Some(new HandlerDirective(pkg, cls)))
  def this(c:String) = this(Some(new HandlerDirective(c)))

  import Presenter._

  Presenter.register(this)

  // Convenience method for converting functions to callbacks.

  implicit def toNativeCallback(f:AccessibilityEvent => Boolean):NativeCallback = new NativeCallback(f)

  /**
   * Maps strings to <code>Callback</code> classes for specific event types 
   * related to the specified package and class.
  */

  val dispatches = collection.mutable.Map[String, Callback]()

  // Register <code>Callback</code> instances for the various <code>AccessibilityEvent</code> types.

  protected def onAnnouncement(c:Callback) = dispatches(dispatchers(TYPE_ANNOUNCEMENT)) = c

  protected def onNotificationStateChanged(c:Callback) = dispatches(dispatchers(TYPE_NOTIFICATION_STATE_CHANGED)) = c

  protected def onTouchExplorationGestureEnd(c:Callback) = dispatches(dispatchers(TYPE_TOUCH_EXPLORATION_GESTURE_END)) = c

  protected def onTouchExplorationGestureStart(c:Callback) = dispatches(dispatchers(TYPE_TOUCH_EXPLORATION_GESTURE_START)) = c

  protected def onViewAccessibilityFocusCleared(c:Callback) = dispatches(dispatchers(TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED)) = c

  protected def onViewAccessibilityFocused(c:Callback) = dispatches(dispatchers(TYPE_VIEW_ACCESSIBILITY_FOCUSED)) = c

  protected def onViewClicked(c:Callback) = dispatches(dispatchers(TYPE_VIEW_CLICKED)) = c

  protected def onViewFocused(c:Callback) = dispatches(dispatchers(TYPE_VIEW_FOCUSED)) = c

  protected def onViewHoverEnter(c:Callback) = dispatches(dispatchers(TYPE_VIEW_HOVER_ENTER)) = c

  protected def onViewHoverExit(c:Callback) = dispatches(dispatchers(TYPE_VIEW_HOVER_EXIT)) = c

  protected def onViewLongClicked(c:Callback) = dispatches(dispatchers(TYPE_VIEW_LONG_CLICKED)) = c

  protected def onViewScrolled(c:Callback) = dispatches(dispatchers(TYPE_VIEW_SCROLLED)) = c

  protected def onViewSelected(c:Callback) = dispatches(dispatchers(TYPE_VIEW_SELECTED)) = c

  protected def onViewTextChanged(c:Callback) = dispatches(dispatchers(TYPE_VIEW_TEXT_CHANGED)) = c

  protected def onViewTextSelectionChanged(c:Callback) = dispatches(dispatchers(TYPE_VIEW_TEXT_SELECTION_CHANGED)) = c

  protected def onViewTextTraversedAtMovementGranularity(c:Callback) = dispatches(dispatchers(TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY)) = c

  protected def onWindowContentChanged(c:Callback) = dispatches(dispatchers(TYPE_WINDOW_CONTENT_CHANGED)) = c

  protected def onWindowStateChanged(c:Callback) = dispatches(dispatchers(TYPE_WINDOW_STATE_CHANGED)) = c

  // Called if no other events match.

  protected def byDefault(c:Callback) = dispatches("default") = c

  protected def interactables(source:AccessibilityNodeInfo) = 
    (source :: source.descendants).filter(_.interactive_?)

  /**
   * Run a given <code>AccessibilityEvent</code> through this <code>Presenter</code>
   *
   * @return <code>true</code> if processing should stop, <code>false</code> otherwise.
  */

  def apply(payload:EventPayload):Boolean = {

    def dispatchTo(callback:String):Boolean = dispatches.get(callback).map(_(payload.event)).getOrElse(false)

    val fallback = dispatchers.get(payload.eventType).map(dispatchTo(_)).getOrElse(false)

    if(!fallback)
      dispatchTo("default")
    else fallback
  }

}

// Now, finally, we reach the presentation logic for generic Android widgets.

/**
 * Encapsulates generic handling for multiple types of buttons.
*/

trait GenericButtonPresenter extends Presenter {
  onViewFocused { e:AccessibilityEvent =>
    val text = e.utterances(addBlank=false).mkString(": ")
    if(text == "") {
      if(VERSION.SDK_INT >= 14) {
        Option(e.getSource).flatMap { source =>
          val descendants = source.root.descendants
          val index = descendants.indexOf(source)+1
          if(index > 0)
            Some(speak(getString(R.string.listItem, getString(R.string.button), index.toString, descendants.size.toString)))
          else None
        }.getOrElse(speak(getString(R.string.button).toString))
        true
      } else
        speak(getString(R.string.button).toString)
    } else
      speak(getString(R.string.labeledButton, text))
  }
}

/**
 * Run before every event. Cannot pre-empt other Presenters.
*/

object Before extends Presenter {

  onViewHoverEnter { e:AccessibilityEvent =>
    stopSpeaking()
    if(SystemClock.uptimeMillis-e.getEventTime <= 100)
      shortVibration()
    false
  }

  onViewHoverExit { e:AccessibilityEvent =>
    if(SystemClock.uptimeMillis-e.getEventTime <= 100)
      shortVibration()
    false
  }

  /*byDefault { e:AccessibilityEvent =>
    if(VERSION.SDK_INT >= 14) {
      if(e.getRecordCount > 0) {
        Log.d("spielcheck", "E: "+e)
        for(i <- 0 to e.getRecordCount-1) {
          Log.d("spielcheck", e.getRecord(i).toString)
        }
      }
    }
    false
  }*/

}

/**
 * Run after every event.
*/

object After extends Presenter {

  onViewClicked { e:AccessibilityEvent =>
    if(VERSION.SDK_INT >= 16)
      Option(e.getSource).foreach { source =>
        if(source.findFocus(FOCUS_ACCESSIBILITY) == null)
          source.performAction(ACTION_ACCESSIBILITY_FOCUS)
      }
    true
  }

  onViewFocused { e:AccessibilityEvent =>
    if(VERSION.SDK_INT >= 14)
      Option(e.getSource).foreach { source =>
        if(source.getChildCount == 0 && source.interactive_? && !e.isEnabled)
          speak(getString(R.string.disabled), false)
      }
    false
  }

}
<commit_msg>Don't speak 'disabled' for items without text.<commit_after>package info.spielproject.spiel
package presenters

import collection.JavaConversions._

import android.app.{ActivityManager, Service}
import android.content.Context
import android.os.Build.VERSION
import android.os.SystemClock
import android.util.Log
import android.view.accessibility.{AccessibilityEvent, AccessibilityNodeInfo}
import AccessibilityEvent._
import AccessibilityNodeInfo._

import routing._

/**
 * Represents code that is called when a specific <code>AccessibilityEvent</code> is received.
*/

abstract class Callback {

  /**
   * Called with an <code>AccessibilityEvent</code>.
   * @return <code>true</code> if event processing should stop, false otherwise
  */

  def apply(e:AccessibilityEvent):Boolean

}

/**
 * Represents a callback that is written in native Scala code.
*/

class NativeCallback(f:AccessibilityEvent => Boolean) extends Callback {
  def apply(e:AccessibilityEvent) = f(e)
}

/**
 * Singleton that stores the 50 most recent <code>AccessibilityEvent</code> objects for review.
*/

object EventReviewQueue extends collection.mutable.Queue[AccessibilityEvent] {

  /**
   * Adds an event to the queue, stripping excess items if necessary.
  */

  def apply(e:AccessibilityEvent) = {
    enqueue(e)
    while(length > 50) dequeue()
  }

}

case class EventPayload(event:AccessibilityEvent, eventType:Int)

/**
 * Maps a given <code>Callback</code> to events originating from a given 
 * package and class.
 *
 * Passing a blank string for either indicates events from all packages or all classes.
*/

class Presenter(directive:Option[HandlerDirective] = None) extends Handler[EventPayload](Presenter, directive) {

  def this(pkg:String, cls:String) = this(Some(new HandlerDirective(pkg, cls)))
  def this(c:String) = this(Some(new HandlerDirective(c)))

  import Presenter._

  Presenter.register(this)

  // Convenience method for converting functions to callbacks.

  implicit def toNativeCallback(f:AccessibilityEvent => Boolean):NativeCallback = new NativeCallback(f)

  /**
   * Maps strings to <code>Callback</code> classes for specific event types 
   * related to the specified package and class.
  */

  val dispatches = collection.mutable.Map[String, Callback]()

  // Register <code>Callback</code> instances for the various <code>AccessibilityEvent</code> types.

  protected def onAnnouncement(c:Callback) = dispatches(dispatchers(TYPE_ANNOUNCEMENT)) = c

  protected def onNotificationStateChanged(c:Callback) = dispatches(dispatchers(TYPE_NOTIFICATION_STATE_CHANGED)) = c

  protected def onTouchExplorationGestureEnd(c:Callback) = dispatches(dispatchers(TYPE_TOUCH_EXPLORATION_GESTURE_END)) = c

  protected def onTouchExplorationGestureStart(c:Callback) = dispatches(dispatchers(TYPE_TOUCH_EXPLORATION_GESTURE_START)) = c

  protected def onViewAccessibilityFocusCleared(c:Callback) = dispatches(dispatchers(TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED)) = c

  protected def onViewAccessibilityFocused(c:Callback) = dispatches(dispatchers(TYPE_VIEW_ACCESSIBILITY_FOCUSED)) = c

  protected def onViewClicked(c:Callback) = dispatches(dispatchers(TYPE_VIEW_CLICKED)) = c

  protected def onViewFocused(c:Callback) = dispatches(dispatchers(TYPE_VIEW_FOCUSED)) = c

  protected def onViewHoverEnter(c:Callback) = dispatches(dispatchers(TYPE_VIEW_HOVER_ENTER)) = c

  protected def onViewHoverExit(c:Callback) = dispatches(dispatchers(TYPE_VIEW_HOVER_EXIT)) = c

  protected def onViewLongClicked(c:Callback) = dispatches(dispatchers(TYPE_VIEW_LONG_CLICKED)) = c

  protected def onViewScrolled(c:Callback) = dispatches(dispatchers(TYPE_VIEW_SCROLLED)) = c

  protected def onViewSelected(c:Callback) = dispatches(dispatchers(TYPE_VIEW_SELECTED)) = c

  protected def onViewTextChanged(c:Callback) = dispatches(dispatchers(TYPE_VIEW_TEXT_CHANGED)) = c

  protected def onViewTextSelectionChanged(c:Callback) = dispatches(dispatchers(TYPE_VIEW_TEXT_SELECTION_CHANGED)) = c

  protected def onViewTextTraversedAtMovementGranularity(c:Callback) = dispatches(dispatchers(TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY)) = c

  protected def onWindowContentChanged(c:Callback) = dispatches(dispatchers(TYPE_WINDOW_CONTENT_CHANGED)) = c

  protected def onWindowStateChanged(c:Callback) = dispatches(dispatchers(TYPE_WINDOW_STATE_CHANGED)) = c

  // Called if no other events match.

  protected def byDefault(c:Callback) = dispatches("default") = c

  protected def interactables(source:AccessibilityNodeInfo) = 
    (source :: source.descendants).filter(_.interactive_?)

  /**
   * Run a given <code>AccessibilityEvent</code> through this <code>Presenter</code>
   *
   * @return <code>true</code> if processing should stop, <code>false</code> otherwise.
  */

  def apply(payload:EventPayload):Boolean = {

    def dispatchTo(callback:String):Boolean = dispatches.get(callback).map(_(payload.event)).getOrElse(false)

    val fallback = dispatchers.get(payload.eventType).map(dispatchTo(_)).getOrElse(false)

    if(!fallback)
      dispatchTo("default")
    else fallback
  }

}

// Now, finally, we reach the presentation logic for generic Android widgets.

/**
 * Encapsulates generic handling for multiple types of buttons.
*/

trait GenericButtonPresenter extends Presenter {
  onViewFocused { e:AccessibilityEvent =>
    val text = e.utterances(addBlank=false).mkString(": ")
    if(text == "") {
      if(VERSION.SDK_INT >= 14) {
        Option(e.getSource).flatMap { source =>
          val descendants = source.root.descendants
          val index = descendants.indexOf(source)+1
          if(index > 0)
            Some(speak(getString(R.string.listItem, getString(R.string.button), index.toString, descendants.size.toString)))
          else None
        }.getOrElse(speak(getString(R.string.button).toString))
        true
      } else
        speak(getString(R.string.button).toString)
    } else
      speak(getString(R.string.labeledButton, text))
  }
}

/**
 * Run before every event. Cannot pre-empt other Presenters.
*/

object Before extends Presenter {

  onViewHoverEnter { e:AccessibilityEvent =>
    stopSpeaking()
    if(SystemClock.uptimeMillis-e.getEventTime <= 100)
      shortVibration()
    false
  }

  onViewHoverExit { e:AccessibilityEvent =>
    if(SystemClock.uptimeMillis-e.getEventTime <= 100)
      shortVibration()
    false
  }

  /*byDefault { e:AccessibilityEvent =>
    if(VERSION.SDK_INT >= 14) {
      if(e.getRecordCount > 0) {
        Log.d("spielcheck", "E: "+e)
        for(i <- 0 to e.getRecordCount-1) {
          Log.d("spielcheck", e.getRecord(i).toString)
        }
      }
    }
    false
  }*/

}

/**
 * Run after every event.
*/

object After extends Presenter {

  onViewClicked { e:AccessibilityEvent =>
    if(VERSION.SDK_INT >= 16)
      Option(e.getSource).foreach { source =>
        if(source.findFocus(FOCUS_ACCESSIBILITY) == null)
          source.performAction(ACTION_ACCESSIBILITY_FOCUS)
      }
    true
  }

  onViewFocused { e:AccessibilityEvent =>
    Option(e.getSource).foreach { source =>
      if(e.utterances(addBlank = false, stripBlanks = true) != Nil && source.getChildCount == 0 && source.interactive_? && !e.isEnabled)
        speak(getString(R.string.disabled), false)
    }
    false
  }

}

{'fraction_non_alphanumeric': 0.10667494138739485, 'fraction_numerical': 0.0031719762791339126, 'mean_word_length': 4.552450229709035, 'pattern_counts': {'":': 2, '<': 57, '<?xml version=': 0, '>': 86, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7368348', 'n_tokens_mistral': 4593, 'n_tokens_neox': 4399, 'n_words': 921}