AccessibleKeyboardViewProxy.java revision 586a15c3f0d44590a5162e0ab4c3c52511f13f26
15ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette/*
25ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * Copyright (C) 2011 The Android Open Source Project
35ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette *
45ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * Licensed under the Apache License, Version 2.0 (the "License"); you may not
55ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * use this file except in compliance with the License. You may obtain a copy of
65ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * the License at
75ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette *
85ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * http://www.apache.org/licenses/LICENSE-2.0
95ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette *
105ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * Unless required by applicable law or agreed to in writing, software
115ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
125ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
135ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * License for the specific language governing permissions and limitations under
145ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * the License.
155ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette */
165ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
175ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverettepackage com.android.inputmethod.accessibility;
185ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
195ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.content.Context;
205ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.content.SharedPreferences;
215ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.graphics.Color;
225ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.graphics.Paint;
235ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.util.Log;
245ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.view.MotionEvent;
255ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.view.ViewConfiguration;
265ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.view.accessibility.AccessibilityEvent;
275ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
285ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport com.android.inputmethod.compat.AccessibilityEventCompatUtils;
295ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport com.android.inputmethod.compat.MotionEventCompatUtils;
30e7759091ddb5ec18268945d70d9212195bf6497bTadashi G. Takaokaimport com.android.inputmethod.keyboard.Key;
315ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport com.android.inputmethod.keyboard.KeyDetector;
32f426cdd5c62452224ac4bb833c3ccf7b26d1a2a8Tadashi G. Takaokaimport com.android.inputmethod.keyboard.LatinKeyboardBaseView;
335ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport com.android.inputmethod.keyboard.PointerTracker;
345ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
355ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverettepublic class AccessibleKeyboardViewProxy {
365ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    private static final String TAG = AccessibleKeyboardViewProxy.class.getSimpleName();
375ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    private static final AccessibleKeyboardViewProxy sInstance = new AccessibleKeyboardViewProxy();
385ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
395ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    // Delay in milliseconds between key press DOWN and UP events
405ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    private static final long DELAY_KEY_PRESS = 10;
415ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
425ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    private int mScaledEdgeSlop;
43f426cdd5c62452224ac4bb833c3ccf7b26d1a2a8Tadashi G. Takaoka    private LatinKeyboardBaseView mView;
445ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    private AccessibleKeyboardActionListener mListener;
4587d7929d142f7c5f1937e12d6fd32a43ab00740eAlan Viverette    private FlickGestureDetector mGestureDetector;
465ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
475ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    private int mLastHoverKeyIndex = KeyDetector.NOT_A_KEY;
485ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    private int mLastX = -1;
495ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    private int mLastY = -1;
505ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
515ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    public static void init(Context context, SharedPreferences prefs) {
525ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        sInstance.initInternal(context, prefs);
535ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        sInstance.mListener = AccessibleInputMethodServiceProxy.getInstance();
545ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    }
555ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
565ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    public static AccessibleKeyboardViewProxy getInstance() {
575ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        return sInstance;
585ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    }
595ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
60f426cdd5c62452224ac4bb833c3ccf7b26d1a2a8Tadashi G. Takaoka    public static void setView(LatinKeyboardBaseView view) {
615ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        sInstance.mView = view;
625ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    }
635ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
645ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    private AccessibleKeyboardViewProxy() {
655ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        // Not publicly instantiable.
665ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    }
675ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
685ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    private void initInternal(Context context, SharedPreferences prefs) {
695ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        final Paint paint = new Paint();
705ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        paint.setTextAlign(Paint.Align.LEFT);
715ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        paint.setTextSize(14.0f);
725ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        paint.setAntiAlias(true);
735ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        paint.setColor(Color.YELLOW);
745ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
7587d7929d142f7c5f1937e12d6fd32a43ab00740eAlan Viverette        mGestureDetector = new KeyboardFlickGestureDetector(context);
765ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        mScaledEdgeSlop = ViewConfiguration.get(context).getScaledEdgeSlop();
775ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    }
785ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
795ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event,
805ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            PointerTracker tracker) {
815ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        if (mView == null) {
825ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            Log.e(TAG, "No keyboard view set!");
835ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            return false;
845ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        }
855ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
865ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        switch (event.getEventType()) {
875ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        case AccessibilityEventCompatUtils.TYPE_VIEW_HOVER_ENTER:
885ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            final Key key = tracker.getKey(mLastHoverKeyIndex);
895ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
905ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            if (key == null)
915ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette                break;
925ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
935ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            final CharSequence description = KeyCodeDescriptionMapper.getInstance()
945ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette                    .getDescriptionForKey(mView.getContext(), mView.getKeyboard(), key);
955ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
965ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            if (description == null)
975ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette                return false;
985ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
995ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            event.getText().add(description);
1005ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
1015ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            break;
1025ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        }
1035ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
1045ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        return true;
1055ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    }
1065ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
1075ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    /**
108586a15c3f0d44590a5162e0ab4c3c52511f13f26Alan Viverette     * Receives hover events when accessibility is turned on in SDK versions ICS
109586a15c3f0d44590a5162e0ab4c3c52511f13f26Alan Viverette     * and higher.
1105ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     *
1115ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     * @param event The hover event.
1125ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     * @return {@code true} if the event is handled
1135ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     */
114586a15c3f0d44590a5162e0ab4c3c52511f13f26Alan Viverette    public boolean dispatchHoverEvent(MotionEvent event, PointerTracker tracker) {
11587d7929d142f7c5f1937e12d6fd32a43ab00740eAlan Viverette        if (mGestureDetector.onHoverEvent(event, this, tracker))
11687d7929d142f7c5f1937e12d6fd32a43ab00740eAlan Viverette            return true;
11787d7929d142f7c5f1937e12d6fd32a43ab00740eAlan Viverette
11887d7929d142f7c5f1937e12d6fd32a43ab00740eAlan Viverette        return onHoverEventInternal(event, tracker);
1195ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    }
1205ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
1215ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    public boolean dispatchTouchEvent(MotionEvent event) {
1225ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        // Since touch exploration translates hover double-tap to a regular
1235ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        // single-tap, we're going to drop non-touch exploration events.
1245ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        if (!AccessibilityUtils.getInstance().isTouchExplorationEvent(event))
1255ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            return true;
1265ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
1275ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        return false;
1285ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    }
1295ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
1305ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    /**
1315ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     * Handles touch exploration events when Accessibility is turned on.
1325ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     *
1335ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     * @param event The touch exploration hover event.
1345ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     * @return {@code true} if the event was handled
1355ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     */
13687d7929d142f7c5f1937e12d6fd32a43ab00740eAlan Viverette    /*package*/ boolean onHoverEventInternal(MotionEvent event, PointerTracker tracker) {
1375ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        final int x = (int) event.getX();
1385ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        final int y = (int) event.getY();
1395ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
1405ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        switch (event.getAction()) {
1415ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        case MotionEventCompatUtils.ACTION_HOVER_ENTER:
1425ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        case MotionEventCompatUtils.ACTION_HOVER_MOVE:
1435ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            final int keyIndex = tracker.getKeyIndexOn(x, y);
1445ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
1455ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            if (keyIndex != mLastHoverKeyIndex) {
1465ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette                fireKeyHoverEvent(tracker, mLastHoverKeyIndex, false);
1475ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette                mLastHoverKeyIndex = keyIndex;
1485ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette                mLastX = x;
1495ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette                mLastY = y;
1505ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette                fireKeyHoverEvent(tracker, mLastHoverKeyIndex, true);
1515ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            }
1525ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
1535ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            return true;
1545ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        case MotionEventCompatUtils.ACTION_HOVER_EXIT:
1555ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            final int width = mView.getWidth();
1565ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            final int height = mView.getHeight();
1575ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
1585ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            if (x < mScaledEdgeSlop || y < mScaledEdgeSlop || x >= (width - mScaledEdgeSlop)
1595ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette                    || y >= (height - mScaledEdgeSlop)) {
1605ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette                fireKeyHoverEvent(tracker, mLastHoverKeyIndex, false);
1615ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette                mLastHoverKeyIndex = KeyDetector.NOT_A_KEY;
1625ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette                mLastX = -1;
1635ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette                mLastY = -1;
1645ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            } else if (mLastHoverKeyIndex != KeyDetector.NOT_A_KEY) {
1655ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette                fireKeyPressEvent(tracker, mLastX, mLastY, event.getEventTime());
1665ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            }
1675ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
1685ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            return true;
1695ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        }
1705ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
1715ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        return false;
1725ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    }
1735ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
1745ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    private void fireKeyHoverEvent(PointerTracker tracker, int keyIndex, boolean entering) {
1755ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        if (mListener == null) {
1765ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            Log.e(TAG, "No accessible keyboard action listener set!");
1775ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            return;
1785ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        }
1795ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
1805ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        if (mView == null) {
1815ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            Log.e(TAG, "No keyboard view set!");
1825ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            return;
1835ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        }
1845ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
1855ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        if (keyIndex == KeyDetector.NOT_A_KEY)
1865ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            return;
1875ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
1885ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        final Key key = tracker.getKey(keyIndex);
1895ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
1905ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        if (key == null)
1915ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            return;
1925ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
1935ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        if (entering) {
1945ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            mListener.onHoverEnter(key.mCode);
1955ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            mView.sendAccessibilityEvent(AccessibilityEventCompatUtils.TYPE_VIEW_HOVER_ENTER);
1965ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        } else {
1975ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            mListener.onHoverExit(key.mCode);
1985ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            mView.sendAccessibilityEvent(AccessibilityEventCompatUtils.TYPE_VIEW_HOVER_EXIT);
1995ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        }
2005ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    }
2015ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
2025ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    private void fireKeyPressEvent(PointerTracker tracker, int x, int y, long eventTime) {
2030efe174ea43fe576683102effbaef5be27575706Tadashi G. Takaoka        tracker.onDownEvent(x, y, eventTime, mView);
204906f03121b6c6a795f35dbc24d2eceac0665f35fTadashi G. Takaoka        tracker.onUpEvent(x, y, eventTime + DELAY_KEY_PRESS);
2055ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    }
20687d7929d142f7c5f1937e12d6fd32a43ab00740eAlan Viverette
20787d7929d142f7c5f1937e12d6fd32a43ab00740eAlan Viverette    private class KeyboardFlickGestureDetector extends FlickGestureDetector {
20887d7929d142f7c5f1937e12d6fd32a43ab00740eAlan Viverette        public KeyboardFlickGestureDetector(Context context) {
20987d7929d142f7c5f1937e12d6fd32a43ab00740eAlan Viverette            super(context);
21087d7929d142f7c5f1937e12d6fd32a43ab00740eAlan Viverette        }
21187d7929d142f7c5f1937e12d6fd32a43ab00740eAlan Viverette
21287d7929d142f7c5f1937e12d6fd32a43ab00740eAlan Viverette        @Override
21387d7929d142f7c5f1937e12d6fd32a43ab00740eAlan Viverette        public boolean onFlick(MotionEvent e1, MotionEvent e2, int direction) {
21487d7929d142f7c5f1937e12d6fd32a43ab00740eAlan Viverette            if (mListener != null) {
21587d7929d142f7c5f1937e12d6fd32a43ab00740eAlan Viverette                mListener.onFlickGesture(direction);
21687d7929d142f7c5f1937e12d6fd32a43ab00740eAlan Viverette            }
21787d7929d142f7c5f1937e12d6fd32a43ab00740eAlan Viverette            return true;
21887d7929d142f7c5f1937e12d6fd32a43ab00740eAlan Viverette        }
21987d7929d142f7c5f1937e12d6fd32a43ab00740eAlan Viverette    }
2205ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette}
221