Improving accessibility feedback for two state widgets.

1. Added population of sensible text for the state of the
   two state controls such as CheckBox, Switch, etc. This
   is important since if they are in a layout manager which
   fires an accessibility event there should be a description
   of the widget.

bug:5092552

Change-Id: Ie3ca955653563496b84db379ae23a23fe88089a8
diff --git a/core/java/android/preference/CheckBoxPreference.java b/core/java/android/preference/CheckBoxPreference.java
index 166b21b..1536760 100644
--- a/core/java/android/preference/CheckBoxPreference.java
+++ b/core/java/android/preference/CheckBoxPreference.java
@@ -61,8 +61,7 @@
         View checkboxView = view.findViewById(com.android.internal.R.id.checkbox);
         if (checkboxView != null && checkboxView instanceof Checkable) {
             ((Checkable) checkboxView).setChecked(mChecked);
-            // Post this so this view is bound and attached when firing the event.
-            postSendAccessibilityEventForView(checkboxView);
+            sendAccessibilityEvent(checkboxView);
         }
 
         syncSummaryView(view);
diff --git a/core/java/android/preference/SwitchPreference.java b/core/java/android/preference/SwitchPreference.java
index 3dbd522..17f0c1b 100644
--- a/core/java/android/preference/SwitchPreference.java
+++ b/core/java/android/preference/SwitchPreference.java
@@ -102,8 +102,8 @@
         View checkableView = view.findViewById(com.android.internal.R.id.switchWidget);
         if (checkableView != null && checkableView instanceof Checkable) {
             ((Checkable) checkableView).setChecked(mChecked);
-            // Post this so this view is bound and attached when firing the event.
-            postSendAccessibilityEventForView(checkableView);
+
+            sendAccessibilityEvent(checkableView);
 
             if (checkableView instanceof Switch) {
                 final Switch switchView = (Switch) checkableView;
diff --git a/core/java/android/preference/TwoStatePreference.java b/core/java/android/preference/TwoStatePreference.java
index b6f00ad..d186b20 100644
--- a/core/java/android/preference/TwoStatePreference.java
+++ b/core/java/android/preference/TwoStatePreference.java
@@ -37,10 +37,9 @@
     private CharSequence mSummaryOn;
     private CharSequence mSummaryOff;
     boolean mChecked;
-    private boolean mSendAccessibilityEventViewClickedType;
+    private boolean mSendClickAccessibilityEvent;
     private boolean mDisableDependentsState;
 
-    private SendAccessibilityEventTypeViewClicked mSendAccessibilityEventTypeViewClicked;
 
     public TwoStatePreference(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
@@ -60,9 +59,7 @@
 
         boolean newValue = !isChecked();
 
-        // in onBindView() an AccessibilityEventViewClickedType is sent to announce the change
-        // not sending
-        mSendAccessibilityEventViewClickedType = true;
+        mSendClickAccessibilityEvent = true;
 
         if (!callChangeListener(newValue)) {
             return;
@@ -188,26 +185,19 @@
                 : (Boolean) defaultValue);
     }
 
-    /**
-     * Post send an accessibility event for the given view if appropriate.
-     *
-     * @param view View that should send the event
-     */
-    void postSendAccessibilityEventForView(View view) {
-        // send an event to announce the value change of the state. It is done here
-        // because clicking a preference does not immediately change the checked state
-        // for example when enabling the WiFi
-        if (mSendAccessibilityEventViewClickedType
-                && AccessibilityManager.getInstance(getContext()).isEnabled()
-                && view.isEnabled()) {
-            mSendAccessibilityEventViewClickedType = false;
-            if (mSendAccessibilityEventTypeViewClicked == null) {
-                mSendAccessibilityEventTypeViewClicked =
-                    new SendAccessibilityEventTypeViewClicked();
-            }
-            mSendAccessibilityEventTypeViewClicked.mView = view;
-            view.post(mSendAccessibilityEventTypeViewClicked);
+    void sendAccessibilityEvent(View view) {
+        // Since the view is still not attached we create, populate,
+        // and send the event directly since we do not know when it
+        // will be attached and posting commands is not as clean.
+        AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(getContext());
+        if (mSendClickAccessibilityEvent && accessibilityManager.isEnabled()) {
+            AccessibilityEvent event = AccessibilityEvent.obtain();
+            event.setEventType(AccessibilityEvent.TYPE_VIEW_CLICKED);
+            view.onInitializeAccessibilityEvent(event);
+            view.dispatchPopulateAccessibilityEvent(event);
+            accessibilityManager.sendAccessibilityEvent(event);
         }
+        mSendClickAccessibilityEvent = false;
     }
 
     /**
@@ -301,13 +291,4 @@
             }
         };
     }
-
-    private final class SendAccessibilityEventTypeViewClicked implements Runnable {
-        private View mView;
-
-        @Override
-        public void run() {
-            mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
-        }
-    }
 }
diff --git a/core/java/android/widget/CheckBox.java b/core/java/android/widget/CheckBox.java
index b89c2a9..2788846 100644
--- a/core/java/android/widget/CheckBox.java
+++ b/core/java/android/widget/CheckBox.java
@@ -18,6 +18,9 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
+import android.view.accessibility.AccessibilityEvent;
+
+import com.android.internal.R;
 
 
 /**
@@ -65,4 +68,14 @@
     public CheckBox(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
     }
+
+    @Override
+    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+        super.onPopulateAccessibilityEvent(event);
+        if (isChecked()) {
+            event.getText().add(mContext.getString(R.string.checkbox_checked));
+        } else {
+            event.getText().add(mContext.getString(R.string.checkbox_not_checked));
+        }
+    }
 }
diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java
index f3a6da7..7598e54 100644
--- a/core/java/android/widget/CheckedTextView.java
+++ b/core/java/android/widget/CheckedTextView.java
@@ -221,4 +221,14 @@
         super.onInitializeAccessibilityEvent(event);
         event.setChecked(mChecked);
     }
+
+    @Override
+    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+        super.onPopulateAccessibilityEvent(event);
+        if (isChecked()) {
+            event.getText().add(mContext.getString(R.string.radiobutton_selected));
+        } else {
+            event.getText().add(mContext.getString(R.string.radiobutton_not_selected));
+        }
+    }
 }
diff --git a/core/java/android/widget/RadioButton.java b/core/java/android/widget/RadioButton.java
index ebbe1cd..9fa649f 100644
--- a/core/java/android/widget/RadioButton.java
+++ b/core/java/android/widget/RadioButton.java
@@ -18,6 +18,9 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
+import android.view.accessibility.AccessibilityEvent;
+
+import com.android.internal.R;
 
 
 /**
@@ -72,4 +75,14 @@
             super.toggle();
         }
     }
+
+    @Override
+    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+        super.onPopulateAccessibilityEvent(event);
+        if (isChecked()) {
+            event.getText().add(mContext.getString(R.string.radiobutton_selected));
+        } else {
+            event.getText().add(mContext.getString(R.string.radiobutton_not_selected));
+        }
+    }
 }
diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java
index 0c80a11..57f73ac 100644
--- a/core/java/android/widget/Switch.java
+++ b/core/java/android/widget/Switch.java
@@ -364,8 +364,19 @@
     @Override
     public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
         super.onPopulateAccessibilityEvent(event);
-        Layout switchText = getTargetCheckedState() ? mOnLayout : mOffLayout;
-        event.getText().add(switchText.getText());
+        if (isChecked()) {
+            CharSequence text = mOnLayout.getText();
+            if (TextUtils.isEmpty(text)) {
+                text = mContext.getString(R.string.switch_on);
+            }
+            event.getText().add(text);
+        } else {
+            CharSequence text = mOffLayout.getText();
+            if (TextUtils.isEmpty(text)) {
+                text = mContext.getString(R.string.switch_off);
+            }
+            event.getText().add(text);
+        }
     }
 
     private Layout makeLayout(CharSequence text) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 127b6a3..dd5f065 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -8425,6 +8425,9 @@
             if (TextUtils.isEmpty(text)) {
                 text = getHint();
             }
+            if (TextUtils.isEmpty(text)) {
+                text = getContentDescription();
+            }
             if (!TextUtils.isEmpty(text)) {
                 event.getText().add(text);
             }
diff --git a/core/java/android/widget/ToggleButton.java b/core/java/android/widget/ToggleButton.java
index 3b680e8..ba9de24 100644
--- a/core/java/android/widget/ToggleButton.java
+++ b/core/java/android/widget/ToggleButton.java
@@ -22,6 +22,9 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.LayerDrawable;
 import android.util.AttributeSet;
+import android.view.accessibility.AccessibilityEvent;
+
+import com.android.internal.R;
 
 /**
  * Displays checked/unchecked states as a button
@@ -146,5 +149,14 @@
             mIndicatorDrawable.setAlpha(isEnabled() ? NO_ALPHA : (int) (NO_ALPHA * mDisabledAlpha));
         }
     }
-    
+
+    @Override
+    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+        super.onPopulateAccessibilityEvent(event);
+        if (isChecked()) {
+            event.getText().add(mContext.getString(R.string.togglebutton_pressed));
+        } else {
+            event.getText().add(mContext.getString(R.string.togglebutton_not_pressed));
+        }
+    }
 }
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d698341..4c680c1 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3001,6 +3001,30 @@
     <!-- Description of the button to decrement the NumberPicker value. [CHAR LIMIT=NONE] -->
     <string name="number_picker_decrement_button">Decrement</string>
 
+    <!-- CheckBox - accessibility support -->
+    <!-- Description of the checked state of a CheckBox. [CHAR LIMIT=NONE] -->
+    <string name="checkbox_checked">checked</string>
+    <!-- Description of the not checked state of a CheckBox. [CHAR LIMIT=NONE] -->
+    <string name="checkbox_not_checked">not checked</string>
+
+    <!-- RadioButton/CheckedTextView - accessibility support -->
+    <!-- Description of the selected state of a RadioButton. [CHAR LIMIT=NONE] -->
+    <string name="radiobutton_selected">selected</string>
+    <!-- Description of the not selected state of a RadioButton. [CHAR LIMIT=NONE] -->
+    <string name="radiobutton_not_selected">not selected</string>
+
+    <!-- Switch - accessibility support -->
+    <!-- Description of the on state of a Switch. [CHAR LIMIT=NONE] -->
+    <string name="switch_on">on</string>
+    <!-- Description of the off state of a Switch. [CHAR LIMIT=NONE] -->
+    <string name="switch_off">off</string>
+
+    <!-- ToggleButton - accessibility support -->
+    <!-- Description of the pressed state of a ToggleButton. [CHAR LIMIT=NONE] -->
+    <string name="togglebutton_pressed">pressed</string>
+    <!-- Description of the not pressed state of a ToggleButton. [CHAR LIMIT=NONE] -->
+    <string name="togglebutton_not_pressed">not pressed</string>
+
     <!-- Content description for the action bar "home" affordance. [CHAR LIMIT=NONE] -->
     <string name="action_bar_home_description">Navigate home</string>
     <!-- Content description for the action bar "up" affordance. [CHAR LIMIT=NONE] -->