Accessibility: properly announce checkbox/switch preference changes

If a switch/checkbox preference is clicked it immediately fires an
accessibility event which captures the current state of the view to
which the prefernce is bound. If such a preference is a part of a pref
screen clicking it changes the preference and invalidates the adapter
which requests an async layout during which the correspinding view
will be updated. As a result the click accessibility event and node
infos in the view subtree capture the old state of the preference's
view resulting in an opposite feedback - bad. Now if accessibility is
enabled we immediately sync the view with the preference to ensure
the accessibility state reflects the latests view state.

bug:22596300

Change-Id: Idd6fb1c4143c15b62ce8c53fb118983f583305c0
diff --git a/v14/preference/src/android/support/v14/preference/SwitchPreference.java b/v14/preference/src/android/support/v14/preference/SwitchPreference.java
index 048bc6c..1a46cc4 100644
--- a/v14/preference/src/android/support/v14/preference/SwitchPreference.java
+++ b/v14/preference/src/android/support/v14/preference/SwitchPreference.java
@@ -23,6 +23,7 @@
 import android.support.v7.preference.TwoStatePreference;
 import android.util.AttributeSet;
 import android.view.View;
+import android.view.accessibility.AccessibilityManager;
 import android.widget.Checkable;
 import android.widget.CompoundButton;
 import android.widget.Switch;
@@ -135,24 +136,8 @@
     @Override
     public void onBindViewHolder(PreferenceViewHolder holder) {
         super.onBindViewHolder(holder);
-
-        View checkableView = holder.findViewById(R.id.switchWidget);
-        if (checkableView != null && checkableView instanceof Checkable) {
-            if (checkableView instanceof Switch) {
-                final Switch switchView = (Switch) checkableView;
-                switchView.setOnCheckedChangeListener(null);
-            }
-
-            ((Checkable) checkableView).setChecked(mChecked);
-
-            if (checkableView instanceof Switch) {
-                final Switch switchView = (Switch) checkableView;
-                switchView.setTextOn(mSwitchOn);
-                switchView.setTextOff(mSwitchOff);
-                switchView.setOnCheckedChangeListener(mListener);
-            }
-        }
-
+        View switchView = holder.findViewById(R.id.switchWidget);
+        syncSwitchView(switchView);
         syncSummaryView(holder);
     }
 
@@ -211,4 +196,44 @@
     public CharSequence getSwitchTextOff() {
         return mSwitchOff;
     }
+
+    /**
+     * @hide
+     */
+    @Override
+    protected void performClick(View view) {
+        super.performClick(view);
+        syncViewIfAccessibilityEnabled(view);
+    }
+
+
+    private void syncViewIfAccessibilityEnabled(View view) {
+        AccessibilityManager accessibilityManager = (AccessibilityManager)
+                getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+        if (!accessibilityManager.isEnabled()) {
+            return;
+        }
+
+        View switchView = view.findViewById(R.id.switchWidget);
+        syncSwitchView(switchView);
+
+        View summaryView = view.findViewById(android.R.id.summary);
+        syncSummaryView(summaryView);
+    }
+
+    private void syncSwitchView(View view) {
+        if (view instanceof Switch) {
+            final Switch switchView = (Switch) view;
+            switchView.setOnCheckedChangeListener(null);
+        }
+        if (view instanceof Checkable) {
+            ((Checkable) view).setChecked(mChecked);
+        }
+        if (view instanceof Switch) {
+            final Switch switchView = (Switch) view;
+            switchView.setTextOn(mSwitchOn);
+            switchView.setTextOff(mSwitchOff);
+            switchView.setOnCheckedChangeListener(mListener);
+        }
+    }
 }
diff --git a/v7/preference/src/android/support/v7/preference/CheckBoxPreference.java b/v7/preference/src/android/support/v7/preference/CheckBoxPreference.java
index f49b0d6..61eb13c 100644
--- a/v7/preference/src/android/support/v7/preference/CheckBoxPreference.java
+++ b/v7/preference/src/android/support/v7/preference/CheckBoxPreference.java
@@ -21,6 +21,7 @@
 import android.support.v4.content.res.TypedArrayUtils;
 import android.util.AttributeSet;
 import android.view.View;
+import android.view.accessibility.AccessibilityManager;
 import android.widget.Checkable;
 
 /**
@@ -78,4 +79,33 @@
 
         syncSummaryView(holder);
     }
+
+    /**
+     * @hide
+     */
+    @Override
+    protected void performClick(View view) {
+        super.performClick(view);
+        syncViewIfAccessibilityEnabled(view);
+    }
+
+    private void syncViewIfAccessibilityEnabled(View view) {
+        AccessibilityManager accessibilityManager = (AccessibilityManager)
+                getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+        if (!accessibilityManager.isEnabled()) {
+            return;
+        }
+
+        View checkboxView = view.findViewById(R.id.checkbox);
+        syncCheckboxView(checkboxView);
+
+        View summaryView = view.findViewById(android.R.id.summary);
+        syncSummaryView(summaryView);
+    }
+
+    private void syncCheckboxView(View view) {
+        if (view instanceof Checkable) {
+            ((Checkable) view).setChecked(mChecked);
+        }
+    }
 }
diff --git a/v7/preference/src/android/support/v7/preference/Preference.java b/v7/preference/src/android/support/v7/preference/Preference.java
index 15033ff..ede1d71 100644
--- a/v7/preference/src/android/support/v7/preference/Preference.java
+++ b/v7/preference/src/android/support/v7/preference/Preference.java
@@ -133,7 +133,7 @@
     private final View.OnClickListener mClickListener = new View.OnClickListener() {
         @Override
         public void onClick(View v) {
-            performClick();
+            performClick(v);
         }
     };
 
@@ -918,6 +918,13 @@
     }
 
     /**
+     * @hide
+     */
+    protected void performClick(View view) {
+        performClick();
+    }
+
+    /**
      * Called when a click should be performed.
      *
      * @hide
diff --git a/v7/preference/src/android/support/v7/preference/SwitchPreferenceCompat.java b/v7/preference/src/android/support/v7/preference/SwitchPreferenceCompat.java
index 2e0e38b..295d835 100644
--- a/v7/preference/src/android/support/v7/preference/SwitchPreferenceCompat.java
+++ b/v7/preference/src/android/support/v7/preference/SwitchPreferenceCompat.java
@@ -22,6 +22,7 @@
 import android.support.v7.widget.SwitchCompat;
 import android.util.AttributeSet;
 import android.view.View;
+import android.view.accessibility.AccessibilityManager;
 import android.widget.Checkable;
 import android.widget.CompoundButton;
 
@@ -133,24 +134,8 @@
     @Override
     public void onBindViewHolder(PreferenceViewHolder holder) {
         super.onBindViewHolder(holder);
-
-        View checkableView = holder.findViewById(R.id.switchWidget);
-        if (checkableView != null && checkableView instanceof Checkable) {
-            if (checkableView instanceof SwitchCompat) {
-                final SwitchCompat switchView = (SwitchCompat) checkableView;
-                switchView.setOnCheckedChangeListener(null);
-            }
-
-            ((Checkable) checkableView).setChecked(mChecked);
-
-            if (checkableView instanceof SwitchCompat) {
-                final SwitchCompat switchView = (SwitchCompat) checkableView;
-                switchView.setTextOn(mSwitchOn);
-                switchView.setTextOff(mSwitchOff);
-                switchView.setOnCheckedChangeListener(mListener);
-            }
-        }
-
+        View switchView = holder.findViewById(R.id.switchWidget);
+        syncSwitchView(switchView);
         syncSummaryView(holder);
     }
 
@@ -209,4 +194,43 @@
     public CharSequence getSwitchTextOff() {
         return mSwitchOff;
     }
+
+    /**
+     * @hide
+     */
+    @Override
+    protected void performClick(View view) {
+        super.performClick(view);
+        syncViewIfAccessibilityEnabled(view);
+    }
+
+    private void syncViewIfAccessibilityEnabled(View view) {
+        AccessibilityManager accessibilityManager = (AccessibilityManager)
+                getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+        if (!accessibilityManager.isEnabled()) {
+            return;
+        }
+
+        View switchView = view.findViewById(R.id.switchWidget);
+        syncSwitchView(switchView);
+
+        View summaryView = view.findViewById(android.R.id.summary);
+        syncSummaryView(summaryView);
+    }
+
+    private void syncSwitchView(View view) {
+        if (view instanceof SwitchCompat) {
+            final SwitchCompat switchView = (SwitchCompat) view;
+            switchView.setOnCheckedChangeListener(null);
+        }
+        if (view instanceof Checkable) {
+            ((Checkable) view).setChecked(mChecked);
+        }
+        if (view instanceof SwitchCompat) {
+            final SwitchCompat switchView = (SwitchCompat) view;
+            switchView.setTextOn(mSwitchOn);
+            switchView.setTextOff(mSwitchOff);
+            switchView.setOnCheckedChangeListener(mListener);
+        }
+    }
 }
diff --git a/v7/preference/src/android/support/v7/preference/TwoStatePreference.java b/v7/preference/src/android/support/v7/preference/TwoStatePreference.java
index e7748cc..939a50c 100644
--- a/v7/preference/src/android/support/v7/preference/TwoStatePreference.java
+++ b/v7/preference/src/android/support/v7/preference/TwoStatePreference.java
@@ -193,33 +193,40 @@
      */
     protected void syncSummaryView(PreferenceViewHolder holder) {
         // Sync the summary holder
-        TextView summaryView = (TextView) holder.findViewById(android.R.id.summary);
-        if (summaryView != null) {
-            boolean useDefaultSummary = true;
-            if (mChecked && !TextUtils.isEmpty(mSummaryOn)) {
-                summaryView.setText(mSummaryOn);
-                useDefaultSummary = false;
-            } else if (!mChecked && !TextUtils.isEmpty(mSummaryOff)) {
-                summaryView.setText(mSummaryOff);
+        View view = holder.findViewById(android.R.id.summary);
+        syncSummaryView(view);
+    }
+
+    /**
+     * @hide
+     */
+    protected void syncSummaryView(View view) {
+        if (!(view instanceof TextView)) {
+            return;
+        }
+        TextView summaryView = (TextView) view;
+        boolean useDefaultSummary = true;
+        if (mChecked && !TextUtils.isEmpty(mSummaryOn)) {
+            summaryView.setText(mSummaryOn);
+            useDefaultSummary = false;
+        } else if (!mChecked && !TextUtils.isEmpty(mSummaryOff)) {
+            summaryView.setText(mSummaryOff);
+            useDefaultSummary = false;
+        }
+        if (useDefaultSummary) {
+            final CharSequence summary = getSummary();
+            if (!TextUtils.isEmpty(summary)) {
+                summaryView.setText(summary);
                 useDefaultSummary = false;
             }
-
-            if (useDefaultSummary) {
-                final CharSequence summary = getSummary();
-                if (!TextUtils.isEmpty(summary)) {
-                    summaryView.setText(summary);
-                    useDefaultSummary = false;
-                }
-            }
-
-            int newVisibility = View.GONE;
-            if (!useDefaultSummary) {
-                // Someone has written to it
-                newVisibility = View.VISIBLE;
-            }
-            if (newVisibility != summaryView.getVisibility()) {
-                summaryView.setVisibility(newVisibility);
-            }
+        }
+        int newVisibility = View.GONE;
+        if (!useDefaultSummary) {
+            // Someone has written to it
+            newVisibility = View.VISIBLE;
+        }
+        if (newVisibility != summaryView.getVisibility()) {
+            summaryView.setVisibility(newVisibility);
         }
     }