Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 62316d7e authored by Tadashi G. Takaoka's avatar Tadashi G. Takaoka
Browse files

Support more keys accessibility mode

Bug: 12491371
Change-Id: Ib1fc8affbccfbaca3424ecdc2812f47047106aa2
parent 1a0cd086
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -48,6 +48,7 @@
    <integer name="config_max_longpress_timeout">700</integer>
    <integer name="config_min_longpress_timeout">100</integer>
    <integer name="config_longpress_timeout_step">10</integer>
    <integer name="config_accessibility_long_press_key_timeout">1500</integer>
    <integer name="config_max_more_keys_column">5</integer>
    <integer name="config_more_keys_keyboard_fadein_anim_time">0</integer>
    <integer name="config_more_keys_keyboard_fadeout_anim_time">100</integer>
+5 −0
Original line number Diff line number Diff line
@@ -139,4 +139,9 @@
    <string name="spoken_symbol_unknown">Unknown symbol</string>
    <!-- Spoken description for unknown emoji code point. -->
    <string name="spoken_emoji_unknown">Unknown emoji</string>

    <!-- Spoken descriptions when opening a more keys keyboard that has alternative characters. -->
    <string name="spoken_open_more_keys_keyboard">Alternative characters are available</string>
    <!-- Spoken descriptions when closing a more keys keyboard that has alternative characters. -->
    <string name="spoken_close_more_keys_keyboard">Alternative characters are dismissed</string>
</resources>
+67 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.inputmethod.accessibility;

import android.content.Context;
import android.os.Handler;
import android.os.Message;

import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.latin.R;

// Handling long press timer to show a more keys keyboard.
final class AccessibilityLongPressTimer extends Handler {
    public interface LongPressTimerCallback {
        public void onLongPressed(Key key);
    }

    private static final int MSG_LONG_PRESS = 1;

    private final LongPressTimerCallback mCallback;
    private final long mConfigAccessibilityLongPressTimeout;

    public AccessibilityLongPressTimer(final LongPressTimerCallback callback,
            final Context context) {
        super();
        mCallback = callback;
        mConfigAccessibilityLongPressTimeout = context.getResources().getInteger(
                R.integer.config_accessibility_long_press_key_timeout);
    }

    @Override
    public void handleMessage(final Message msg) {
        switch (msg.what) {
        case MSG_LONG_PRESS:
            cancelLongPress();
            mCallback.onLongPressed((Key)msg.obj);
            return;
        default:
            super.handleMessage(msg);
            return;
        }
    }

    public void startLongPress(final Key key) {
        cancelLongPress();
        final Message longPressMessage = obtainMessage(MSG_LONG_PRESS, key);
        sendMessageDelayed(longPressMessage, mConfigAccessibilityLongPressTimeout);
    }

    public void cancelLongPress() {
        removeMessages(MSG_LONG_PRESS);
    }
}
+39 −9
Original line number Diff line number Diff line
@@ -33,14 +33,29 @@ import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardView;
import com.android.inputmethod.keyboard.PointerTracker;

/**
 * This class represents a delegate that can be registered in a class that extends
 * {@link KeyboardView} to enhance accessibility support via composition rather via inheritance.
 *
 * To implement accessibility mode, the target keyboard view has to:<p>
 * - Call {@link #setKeyboard(Keyboard)} when a new keyboard is set to the keyboard view.
 * - Dispatch a hover event by calling {@link #onHoverEnter(MotionEvent)}.
 *
 * @param <KV> The keyboard view class type.
 */
public class KeyboardAccessibilityDelegate<KV extends KeyboardView>
        extends AccessibilityDelegateCompat {
    private static final String TAG = KeyboardAccessibilityDelegate.class.getSimpleName();
    protected static final boolean DEBUG_HOVER = false;

    protected final KV mKeyboardView;
    protected final KeyDetector mKeyDetector;
    private Keyboard mKeyboard;
    private KeyboardAccessibilityNodeProvider mAccessibilityNodeProvider;
    private Key mLastHoverKey;

    public static final int HOVER_EVENT_POINTER_ID = 0;

    public KeyboardAccessibilityDelegate(final KV keyboardView, final KeyDetector keyDetector) {
        super();
        mKeyboardView = keyboardView;
@@ -180,8 +195,11 @@ public class KeyboardAccessibilityDelegate<KV extends KeyboardView>
     */
    protected void onHoverEnter(final MotionEvent event) {
        final Key key = getHoverKeyOf(event);
        if (DEBUG_HOVER) {
            Log.d(TAG, "onHoverEnter: key=" + key);
        }
        if (key != null) {
            onHoverEnterKey(key);
            onHoverEnterTo(key);
        }
        setLastHoverKey(key);
    }
@@ -196,14 +214,14 @@ public class KeyboardAccessibilityDelegate<KV extends KeyboardView>
        final Key key = getHoverKeyOf(event);
        if (key != lastKey) {
            if (lastKey != null) {
                onHoverExitKey(lastKey);
                onHoverExitFrom(lastKey);
            }
            if (key != null) {
                onHoverEnterKey(key);
                onHoverEnterTo(key);
            }
        }
        if (key != null) {
            onHoverMoveKey(key);
            onHoverMoveWithin(key);
        }
        setLastHoverKey(key);
    }
@@ -215,15 +233,18 @@ public class KeyboardAccessibilityDelegate<KV extends KeyboardView>
     */
    protected void onHoverExit(final MotionEvent event) {
        final Key lastKey = getLastHoverKey();
        if (DEBUG_HOVER) {
            Log.d(TAG, "onHoverExit: key=" + getHoverKeyOf(event) + " last=" + lastKey);
        }
        if (lastKey != null) {
            onHoverExitKey(lastKey);
            onHoverExitFrom(lastKey);
        }
        final Key key = getHoverKeyOf(event);
        // Make sure we're not getting an EXIT event because the user slid
        // off the keyboard area, then force a key press.
        if (key != null) {
            onRegisterHoverKey(key, event);
            onHoverExitKey(key);
            onHoverExitFrom(key);
        }
        setLastHoverKey(null);
    }
@@ -235,6 +256,9 @@ public class KeyboardAccessibilityDelegate<KV extends KeyboardView>
     * @param event A hover exit event that triggers key registering.
     */
    protected void onRegisterHoverKey(final Key key, final MotionEvent event) {
        if (DEBUG_HOVER) {
            Log.d(TAG, "onRegisterHoverKey: key=" + key);
        }
        simulateTouchEvent(MotionEvent.ACTION_DOWN, event);
        simulateTouchEvent(MotionEvent.ACTION_UP, event);
    }
@@ -274,7 +298,10 @@ public class KeyboardAccessibilityDelegate<KV extends KeyboardView>
     *
     * @param key The currently hovered key.
     */
    protected void onHoverEnterKey(final Key key) {
    protected void onHoverEnterTo(final Key key) {
        if (DEBUG_HOVER) {
            Log.d(TAG, "onHoverEnterTo: key=" + key);
        }
        key.onPressed();
        mKeyboardView.invalidateKey(key);
        final KeyboardAccessibilityNodeProvider provider = getAccessibilityNodeProvider();
@@ -287,14 +314,17 @@ public class KeyboardAccessibilityDelegate<KV extends KeyboardView>
     *
     * @param key The currently hovered key.
     */
    protected void onHoverMoveKey(final Key key) { }
    protected void onHoverMoveWithin(final Key key) { }

    /**
     * Handles a hover exit event on a key.
     *
     * @param key The currently hovered key.
     */
    protected void onHoverExitKey(final Key key) {
    protected void onHoverExitFrom(final Key key) {
        if (DEBUG_HOVER) {
            Log.d(TAG, "onHoverExitFrom: key=" + key);
        }
        key.onReleased();
        mKeyboardView.invalidateKey(key);
        final KeyboardAccessibilityNodeProvider provider = getAccessibilityNodeProvider();
+77 −1
Original line number Diff line number Diff line
@@ -17,17 +17,29 @@
package com.android.inputmethod.accessibility;

import android.content.Context;
import android.os.SystemClock;
import android.util.Log;
import android.util.SparseIntArray;
import android.view.MotionEvent;

import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.KeyDetector;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardId;
import com.android.inputmethod.keyboard.MainKeyboardView;
import com.android.inputmethod.keyboard.PointerTracker;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;

/**
 * This class represents a delegate that can be registered in {@link MainKeyboardView} to enhance
 * accessibility support via composition rather via inheritance.
 */
public final class MainKeyboardAccessibilityDelegate
        extends KeyboardAccessibilityDelegate<MainKeyboardView> {
        extends KeyboardAccessibilityDelegate<MainKeyboardView>
        implements AccessibilityLongPressTimer.LongPressTimerCallback {
    private static final String TAG = MainKeyboardAccessibilityDelegate.class.getSimpleName();

    /** Map of keyboard modes to resource IDs. */
    private static final SparseIntArray KEYBOARD_MODE_RES_IDS = new SparseIntArray();

@@ -46,10 +58,15 @@ public final class MainKeyboardAccessibilityDelegate
    /** The most recently set keyboard mode. */
    private int mLastKeyboardMode = KEYBOARD_IS_HIDDEN;
    private static final int KEYBOARD_IS_HIDDEN = -1;
    private boolean mShouldIgnoreOnRegisterHoverKey;

    private final AccessibilityLongPressTimer mAccessibilityLongPressTimer;

    public MainKeyboardAccessibilityDelegate(final MainKeyboardView mainKeyboardView,
            final KeyDetector keyDetector) {
        super(mainKeyboardView, keyDetector);
        mAccessibilityLongPressTimer = new AccessibilityLongPressTimer(
                this /* callback */, mainKeyboardView.getContext());
    }

    /**
@@ -172,4 +189,63 @@ public final class MainKeyboardAccessibilityDelegate
    private void announceKeyboardHidden() {
        sendWindowStateChanged(R.string.announce_keyboard_hidden);
    }

    @Override
    protected void onRegisterHoverKey(final Key key, final MotionEvent event) {
        if (DEBUG_HOVER) {
            Log.d(TAG, "onRegisterHoverKey: key=" + key + " ignore="
                    + mShouldIgnoreOnRegisterHoverKey);
        }
        if (!mShouldIgnoreOnRegisterHoverKey) {
            super.onRegisterHoverKey(key, event);
        }
        mShouldIgnoreOnRegisterHoverKey = false;
    }

    @Override
    protected void onHoverEnterTo(final Key key) {
        if (DEBUG_HOVER) {
            Log.d(TAG, "onHoverEnterTo: key=" + key);
        }
        mAccessibilityLongPressTimer.cancelLongPress();
        super.onHoverEnterTo(key);
        if (key.isLongPressEnabled()) {
            mAccessibilityLongPressTimer.startLongPress(key);
        }
    }

    protected void onHoverExitFrom(final Key key) {
        if (DEBUG_HOVER) {
            Log.d(TAG, "onHoverExitFrom: key=" + key);
        }
        mAccessibilityLongPressTimer.cancelLongPress();
        super.onHoverExitFrom(key);
    }

    @Override
    public void onLongPressed(final Key key) {
        if (DEBUG_HOVER) {
            Log.d(TAG, "onLongPressed: key=" + key);
        }
        final PointerTracker tracker = PointerTracker.getPointerTracker(HOVER_EVENT_POINTER_ID);
        final long eventTime = SystemClock.uptimeMillis();
        final int x = key.getHitBox().centerX();
        final int y = key.getHitBox().centerY();
        final MotionEvent downEvent = MotionEvent.obtain(
                eventTime, eventTime, MotionEvent.ACTION_DOWN, x, y, 0 /* metaState */);
        // Inject a fake down event to {@link PointerTracker} to handle a long press correctly.
        tracker.processMotionEvent(downEvent, mKeyDetector);
        // The above fake down event triggers an unnecessary long press timer that should be
        // canceled.
        tracker.cancelLongPressTimer();
        downEvent.recycle();
        // Invoke {@link MainKeyboardView#onLongPress(PointerTracker)} as if a long press timeout
        // has passed.
        mKeyboardView.onLongPress(tracker);
        // If {@link Key#hasNoPanelAutoMoreKeys()} is true (such as "0 +" key on the phone layout)
        // or a key invokes IME switcher dialog, we should just ignore the next
        // {@link #onRegisterHoverKey(Key,MotionEvent)}. It can be determined by whether
        // {@link PointerTracker} is in operation or not.
        mShouldIgnoreOnRegisterHoverKey = !tracker.isInOperation();
    }
}
Loading