Announce keyboard mode changes as a WINDOW_STATE_CHANGED event.

Bug: 8165295
Change-Id: Ie416f6cdb68377f3e06f30e9b6363c38ba2a602d
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index 68fa8fd..e89174b 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -223,6 +223,29 @@
     <!-- Spoken feedback after changing to the shifted phone dialer (symbols) keyboard. -->
     <string name="spoken_description_mode_phone_shift">Phone symbols mode</string>
 
+    <!-- Spoken feedback when the keyboard is hidden. -->
+    <string name="announce_keyboard_hidden">Keyboard hidden</string>
+    <!-- Spoken feedback when the keyboard mode changes. -->
+    <string name="announce_keyboard_mode">Showing <xliff:g id="mode" example="email">%s</xliff:g> keyboard</string>
+    <!-- Description of the keyboard mode for entering dates. -->
+    <string name="keyboard_mode_date">date</string>
+    <!-- Description of the keyboard mode for entering dates and times. -->
+    <string name="keyboard_mode_date_time">date and time</string>
+    <!-- Description of the keyboard mode for entering email addresses. -->
+    <string name="keyboard_mode_email">email</string>
+    <!-- Description of the keyboard mode for entering text messages. -->
+    <string name="keyboard_mode_im">messaging</string>
+    <!-- Description of the keyboard mode for entering numbers. -->
+    <string name="keyboard_mode_number">number</string>
+    <!-- Description of the keyboard mode for entering phone numbers. -->
+    <string name="keyboard_mode_phone">phone</string>
+    <!-- Description of the keyboard mode for entering generic text. -->
+    <string name="keyboard_mode_text">text</string>
+    <!-- Description of the keyboard mode for entering times. -->
+    <string name="keyboard_mode_time">time</string>
+    <!-- Description of the keyboard mode for entering URLs. -->
+    <string name="keyboard_mode_url">URL</string>
+
     <!-- Preferences item for enabling speech input -->
     <string name="voice_input">Voice input key</string>
 
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
index bf1cea9..ee52de1 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
@@ -80,16 +80,24 @@
     }
 
     /**
+     * Returns {@code true} if accessibility is enabled. Currently, this means
+     * that the kill switch is off and system accessibility is turned on.
+     *
+     * @return {@code true} if accessibility is enabled.
+     */
+    public boolean isAccessibilityEnabled() {
+        return ENABLE_ACCESSIBILITY && mAccessibilityManager.isEnabled();
+    }
+
+    /**
      * Returns {@code true} if touch exploration is enabled. Currently, this
      * means that the kill switch is off, the device supports touch exploration,
-     * and a spoken feedback service is turned on.
+     * and system accessibility is turned on.
      *
      * @return {@code true} if touch exploration is enabled.
      */
     public boolean isTouchExplorationEnabled() {
-        return ENABLE_ACCESSIBILITY
-                && mAccessibilityManager.isEnabled()
-                && mAccessibilityManager.isTouchExplorationEnabled();
+        return isAccessibilityEnabled() && mAccessibilityManager.isTouchExplorationEnabled();
     }
 
     /**
@@ -113,6 +121,7 @@
      *
      * @return {@code true} if the device should obscure password characters.
      */
+    @SuppressWarnings("deprecation")
     public boolean shouldObscureInput(final EditorInfo editorInfo) {
         if (editorInfo == null) return false;
 
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
index d05fd9e..e6b4412 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
@@ -22,8 +22,11 @@
 import android.support.v4.view.ViewCompat;
 import android.support.v4.view.accessibility.AccessibilityEventCompat;
 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.util.SparseIntArray;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewParent;
+import android.view.accessibility.AccessibilityEvent;
 
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
@@ -35,6 +38,21 @@
 public final class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
     private static final AccessibleKeyboardViewProxy sInstance = new AccessibleKeyboardViewProxy();
 
+    /** Map of keyboard modes to resource IDs. */
+    private static final SparseIntArray KEYBOARD_MODE_RES_IDS = new SparseIntArray();
+
+    static {
+        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_DATE, R.string.keyboard_mode_date);
+        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_DATETIME, R.string.keyboard_mode_date_time);
+        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_EMAIL, R.string.keyboard_mode_email);
+        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_IM, R.string.keyboard_mode_im);
+        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_NUMBER, R.string.keyboard_mode_number);
+        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_PHONE, R.string.keyboard_mode_phone);
+        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_TEXT, R.string.keyboard_mode_text);
+        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_TIME, R.string.keyboard_mode_time);
+        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_URL, R.string.keyboard_mode_url);
+    }
+
     private InputMethodService mInputMethod;
     private MainKeyboardView mView;
     private AccessibilityEntityProvider mAccessibilityNodeProvider;
@@ -85,11 +103,75 @@
         mAccessibilityNodeProvider.setView(view);
     }
 
+    /**
+     * Called when the keyboard layout changes.
+     * <p>
+     * <b>Note:</b> This method will be called even if accessibility is not
+     * enabled.
+     */
     public void setKeyboard() {
-        if (mAccessibilityNodeProvider == null) {
+        if (mAccessibilityNodeProvider != null) {
+            mAccessibilityNodeProvider.setKeyboard();
+        }
+
+        // Since this method is called even when accessibility is off, make sure
+        // to check the state before announcing anything.
+        if (AccessibilityUtils.getInstance().isAccessibilityEnabled()) {
+            announceKeyboardMode();
+        }
+    }
+
+    /**
+     * Called when the keyboard is hidden and accessibility is enabled.
+     */
+    public void onHideWindow() {
+        announceKeyboardHidden();
+    }
+
+    /**
+     * Announces which type of keyboard is being displayed. If the keyboard type
+     * is unknown, no announcement is made.
+     */
+    private void announceKeyboardMode() {
+        final Keyboard keyboard = mView.getKeyboard();
+        final int resId = KEYBOARD_MODE_RES_IDS.get(keyboard.mId.mMode);
+        if (resId == 0) {
             return;
         }
-        mAccessibilityNodeProvider.setKeyboard();
+
+        final Context context = mView.getContext();
+        final String keyboardMode = context.getString(resId);
+        final String text = context.getString(R.string.announce_keyboard_mode, keyboardMode);
+
+        sendWindowStateChanged(text);
+    }
+
+    /**
+     * Announces that the keyboard has been hidden.
+     */
+    private void announceKeyboardHidden() {
+        final Context context = mView.getContext();
+        final String text = context.getString(R.string.announce_keyboard_hidden);
+
+        sendWindowStateChanged(text);
+    }
+
+    /**
+     * Sends a window state change event with the specified text.
+     *
+     * @param text
+     */
+    private void sendWindowStateChanged(final String text) {
+        final AccessibilityEvent stateChange = AccessibilityEvent.obtain(
+                AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+        mView.onInitializeAccessibilityEvent(stateChange);
+        stateChange.getText().add(text);
+        stateChange.setContentDescription(null);
+
+        final ViewParent parent = mView.getParent();
+        if (parent != null) {
+            parent.requestSendAccessibilityEvent(mView, stateChange);
+        }
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index e934746..bb7e2d1 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -955,6 +955,10 @@
         LatinImeLogger.commit();
         mKeyboardSwitcher.onHideWindow();
 
+        if (AccessibilityUtils.getInstance().isAccessibilityEnabled()) {
+            AccessibleKeyboardViewProxy.getInstance().onHideWindow();
+        }
+
         if (TRACE) Debug.stopMethodTracing();
         if (mOptionsDialog != null && mOptionsDialog.isShowing()) {
             mOptionsDialog.dismiss();