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

Commit 311ebf33 authored by Dan Zivkovic's avatar Dan Zivkovic Committed by Android Git Automerger
Browse files

am 23574d12: LatinIME portion of "green plus" feature.

* commit '23574d12':
  LatinIME portion of "green plus" feature.
parents 8303c7b9 23574d12
Loading
Loading
Loading
Loading
+0 −24
Original line number Diff line number Diff line
@@ -33,30 +33,6 @@
        android:soundEffectsEnabled="false" />
    <!-- Provide audio and haptic feedback by ourselves based on the keyboard settings.
         We just need to ignore the system's audio and haptic feedback settings. -->
    <LinearLayout
        android:id="@+id/add_to_dictionary_strip"
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginLeft="@dimen/config_suggestions_strip_horizontal_margin"
        android:layout_marginRight="@dimen/config_suggestions_strip_horizontal_margin"
        android:hapticFeedbackEnabled="false"
        android:soundEffectsEnabled="false">
        <TextView
            android:id="@+id/word_to_save"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            style="?attr/suggestionWordStyle" />
        <include
            android:id="@+id/word_to_save_divider"
            layout="@layout/suggestion_divider" />
        <TextView
            android:id="@+id/hint_add_to_dictionary"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center_vertical|start"
            style="?attr/suggestionWordStyle" />
    </LinearLayout>
    <!-- Provide audio and haptic feedback by ourselves based on the keyboard settings.
         We just need to ignore the system's audio and haptic feedback settings. -->
    <LinearLayout
+0 −76
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
**
** Copyright 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.
*/
-->

<resources>
    <!-- The extra margin in dp around the hit area of the commit/add-to-dictionary indicator -->
    <integer name="text_decorator_hit_area_margin_in_dp">
        4
    </integer>

    <!-- Background color to be used to highlight the target text when the add-to-dictionary
         indicator is visible. -->
    <color name="text_decorator_add_to_dictionary_indicator_text_highlight_color">
        #D1E7B7
    </color>

    <!-- Foreground color of the commit indicator. -->
    <color name="text_decorator_add_to_dictionary_indicator_background_color">
        #4EB848
    </color>

    <!-- Foreground color of the add-to-dictionary indicator. -->
    <color name="text_decorator_add_to_dictionary_indicator_foreground_color">
        #FFFFFF
    </color>

    <!-- Viewport size of "text_decorator_add_to_dictionary_indicator_path". -->
    <integer name="text_decorator_add_to_dictionary_indicator_path_size">
        480
    </integer>

    <!-- Coordinates of the closed path to be used to render the add-to-dictionary indicator.
         The format is: X[0], Y[0], X[1], Y[1], ..., X[N-1], Y[N-1] -->
    <integer-array name="text_decorator_add_to_dictionary_indicator_path">
        <item>380</item>
        <item>260</item>
        <item>260</item>
        <item>260</item>
        <item>260</item>
        <item>380</item>
        <item>220</item>
        <item>380</item>
        <item>220</item>
        <item>260</item>
        <item>100</item>
        <item>260</item>
        <item>100</item>
        <item>220</item>
        <item>220</item>
        <item>220</item>
        <item>220</item>
        <item>100</item>
        <item>260</item>
        <item>100</item>
        <item>260</item>
        <item>220</item>
        <item>380</item>
        <item>220</item>
    </integer-array>
</resources>
+0 −1
Original line number Diff line number Diff line
@@ -127,7 +127,6 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions,
            mKeyboardTextsSet.setLocale(mRichImm.getCurrentSubtypeLocales()[0], mThemeContext);
        } catch (KeyboardLayoutSetException e) {
            Log.w(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause());
            return;
        }
    }

+0 −372
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.keyboard;

import android.graphics.Matrix;
import android.graphics.RectF;
import android.inputmethodservice.InputMethodService;
import android.os.Message;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.inputmethod.CursorAnchorInfo;

import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.compat.CursorAnchorInfoCompatWrapper;
import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

/**
 * A controller class of the add-to-dictionary indicator (a.k.a. TextDecorator). This class
 * is designed to be independent of UI subsystems such as {@link View}. All the UI related
 * operations are delegated to {@link TextDecoratorUi} via {@link TextDecoratorUiOperator}.
 */
public class TextDecorator {
    private static final String TAG = TextDecorator.class.getSimpleName();
    private static final boolean DEBUG = false;

    private static final int INVALID_CURSOR_INDEX = -1;

    private static final int MODE_MONITOR = 0;
    private static final int MODE_WAITING_CURSOR_INDEX = 1;
    private static final int MODE_SHOWING_INDICATOR = 2;

    private int mMode = MODE_MONITOR;

    private String mLastComposingText = null;
    private boolean mHasRtlCharsInLastComposingText = false;
    private RectF mComposingTextBoundsForLastComposingText = new RectF();

    private boolean mIsFullScreenMode = false;
    private String mWaitingWord = null;
    private int mWaitingCursorStart = INVALID_CURSOR_INDEX;
    private int mWaitingCursorEnd = INVALID_CURSOR_INDEX;
    @Nullable
    private CursorAnchorInfoCompatWrapper mCursorAnchorInfoWrapper = null;

    @Nonnull
    private final Listener mListener;

    @Nonnull
    private TextDecoratorUiOperator mUiOperator = EMPTY_UI_OPERATOR;

    public interface Listener {
        /**
         * Called when the user clicks the indicator to add the word into the dictionary.
         * @param word the word which the user clicked on.
         */
        void onClickComposingTextToAddToDictionary(final String word);
    }

    public TextDecorator(@Nullable final Listener listener) {
        mListener = (listener != null) ? listener : EMPTY_LISTENER;
    }

    /**
     * Sets the UI operator for {@link TextDecorator}. Any user visible operations will be
     * delegated to the associated UI operator.
     * @param uiOperator the UI operator to be associated.
     */
    public void setUiOperator(@Nonnull final TextDecoratorUiOperator uiOperator) {
        mUiOperator.disposeUi();
        mUiOperator = uiOperator;
        mUiOperator.setOnClickListener(getOnClickHandler());
    }

    private final Runnable mDefaultOnClickHandler = new Runnable() {
        @Override
        public void run() {
            onClickIndicator();
        }
    };

    @UsedForTesting
    final Runnable getOnClickHandler() {
        return mDefaultOnClickHandler;
    }

    /**
     * Shows the "Add to dictionary" indicator and associates it with associating the given word.
     *
     * @param word the word which should be associated with the indicator. This object will be
     * passed back in {@link Listener#onClickComposingTextToAddToDictionary(String)}.
     * @param selectionStart the cursor index (inclusive) when the indicator should be displayed.
     * @param selectionEnd the cursor index (exclusive) when the indicator should be displayed.
     */
    public void showAddToDictionaryIndicator(final String word, final int selectionStart,
            final int selectionEnd) {
        mWaitingWord = word;
        mWaitingCursorStart = selectionStart;
        mWaitingCursorEnd = selectionEnd;
        mMode = MODE_WAITING_CURSOR_INDEX;
        layoutLater();
        return;
    }

    /**
     * Must be called when the input method is about changing to for from the full screen mode.
     * @param fullScreenMode {@code true} if the input method is entering the full screen mode.
     * {@code false} is the input method is finishing the full screen mode.
     */
    public void notifyFullScreenMode(final boolean fullScreenMode) {
        final boolean fullScreenModeChanged = (mIsFullScreenMode != fullScreenMode);
        mIsFullScreenMode = fullScreenMode;
        if (fullScreenModeChanged) {
            layoutLater();
        }
    }

    /**
     * Resets previous requests and makes indicator invisible.
     */
    public void reset() {
        mWaitingWord = null;
        mMode = MODE_MONITOR;
        mWaitingCursorStart = INVALID_CURSOR_INDEX;
        mWaitingCursorEnd = INVALID_CURSOR_INDEX;
        cancelLayoutInternalExpectedly("Resetting internal state.");
    }

    /**
     * Must be called when the {@link InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)}
     * is called.
     *
     * <p>CAVEAT: Currently the input method author is responsible for ignoring
     * {@link InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)} called in full screen
     * mode.</p>
     * @param info the compatibility wrapper object for the received {@link CursorAnchorInfo}.
     */
    public void onUpdateCursorAnchorInfo(@Nullable final CursorAnchorInfoCompatWrapper info) {
        mCursorAnchorInfoWrapper = info;
        // Do not use layoutLater() to minimize the latency.
        layoutImmediately();
    }

    private void cancelLayoutInternalUnexpectedly(final String message) {
        mUiOperator.hideUi();
        Log.d(TAG, message);
    }

    private void cancelLayoutInternalExpectedly(final String message) {
        mUiOperator.hideUi();
        if (DEBUG) {
            Log.d(TAG, message);
        }
    }

    private void layoutLater() {
        mLayoutInvalidator.invalidateLayout();
    }


    private void layoutImmediately() {
        // Clear pending layout requests.
        mLayoutInvalidator.cancelInvalidateLayout();
        layoutMain();
    }

    void layoutMain() {
        final CursorAnchorInfoCompatWrapper info = mCursorAnchorInfoWrapper;

        if (info == null) {
            cancelLayoutInternalExpectedly("CursorAnchorInfo isn't available.");
            return;
        }

        final Matrix matrix = info.getMatrix();
        if (matrix == null) {
            cancelLayoutInternalUnexpectedly("Matrix is null");
        }

        final CharSequence composingText = info.getComposingText();
        if (!TextUtils.isEmpty(composingText)) {
            final int composingTextStart = info.getComposingTextStart();
            final int lastCharRectIndex = composingTextStart + composingText.length() - 1;
            final RectF lastCharRect = info.getCharacterBounds(lastCharRectIndex);
            final int lastCharRectFlags = info.getCharacterBoundsFlags(lastCharRectIndex);
            final boolean hasInvisibleRegionInLastCharRect =
                    (lastCharRectFlags & CursorAnchorInfoCompatWrapper.FLAG_HAS_INVISIBLE_REGION)
                            != 0;
            if (lastCharRect == null || matrix == null || hasInvisibleRegionInLastCharRect) {
                mUiOperator.hideUi();
                return;
            }

            // Note that the following layout information is fragile, and must be invalidated
            // even when surrounding text next to the composing text is changed because it can
            // affect how the composing text is rendered.
            // TODO: Investigate if we can change the input logic to make the target text
            // composing state so that we can retrieve the character bounds reliably.
            final String composingTextString = composingText.toString();
            final float top = lastCharRect.top;
            final float bottom = lastCharRect.bottom;
            float left = lastCharRect.left;
            float right = lastCharRect.right;
            boolean useRtlLayout = false;
            for (int i = composingText.length() - 1; i >= 0; --i) {
                final int characterIndex = composingTextStart + i;
                final RectF characterBounds = info.getCharacterBounds(characterIndex);
                final int characterBoundsFlags = info.getCharacterBoundsFlags(characterIndex);
                if (characterBounds == null) {
                    break;
                }
                if (characterBounds.top != top) {
                    break;
                }
                if (characterBounds.bottom != bottom) {
                    break;
                }
                if ((characterBoundsFlags & CursorAnchorInfoCompatWrapper.FLAG_IS_RTL) != 0) {
                    // This is for both RTL text and bi-directional text. RTL languages usually mix
                    // RTL characters with LTR characters and in this case we should display the
                    // indicator on the left, while in LTR languages that normally never happens.
                    // TODO: Try to come up with a better algorithm.
                    useRtlLayout = true;
                }
                left = Math.min(characterBounds.left, left);
                right = Math.max(characterBounds.right, right);
            }
            mLastComposingText = composingTextString;
            mHasRtlCharsInLastComposingText = useRtlLayout;
            mComposingTextBoundsForLastComposingText.set(left, top, right, bottom);
        }

        final int selectionStart = info.getSelectionStart();
        final int selectionEnd = info.getSelectionEnd();
        switch (mMode) {
            case MODE_MONITOR:
                mUiOperator.hideUi();
                return;
            case MODE_WAITING_CURSOR_INDEX:
                if (selectionStart != mWaitingCursorStart || selectionEnd != mWaitingCursorEnd) {
                    mUiOperator.hideUi();
                    return;
                }
                mMode = MODE_SHOWING_INDICATOR;
                break;
            case MODE_SHOWING_INDICATOR:
                if (selectionStart != mWaitingCursorStart || selectionEnd != mWaitingCursorEnd) {
                    mUiOperator.hideUi();
                    mMode = MODE_MONITOR;
                    mWaitingCursorStart = INVALID_CURSOR_INDEX;
                    mWaitingCursorEnd = INVALID_CURSOR_INDEX;
                    return;
                }
                break;
            default:
                cancelLayoutInternalUnexpectedly("Unexpected internal mode=" + mMode);
                return;
        }

        if (!TextUtils.equals(mLastComposingText, mWaitingWord)) {
            cancelLayoutInternalUnexpectedly("mLastComposingText doesn't match mWaitingWord");
            return;
        }

        if ((info.getInsertionMarkerFlags() &
                CursorAnchorInfoCompatWrapper.FLAG_HAS_INVISIBLE_REGION) != 0) {
            mUiOperator.hideUi();
            return;
        }

        mUiOperator.layoutUi(matrix, mComposingTextBoundsForLastComposingText,
                mHasRtlCharsInLastComposingText);
    }

    void onClickIndicator() {
        if (mMode != MODE_SHOWING_INDICATOR) {
            return;
        }
        mListener.onClickComposingTextToAddToDictionary(mWaitingWord);
    }

    private final LayoutInvalidator mLayoutInvalidator = new LayoutInvalidator(this);

    /**
     * Used for managing pending layout tasks for {@link TextDecorator#layoutLater()}.
     */
    private static final class LayoutInvalidator {
        private final HandlerImpl mHandler;
        public LayoutInvalidator(@Nonnull final TextDecorator ownerInstance) {
            mHandler = new HandlerImpl(ownerInstance);
        }

        private static final int MSG_LAYOUT = 0;

        private static final class HandlerImpl
                extends LeakGuardHandlerWrapper<TextDecorator> {
            public HandlerImpl(@Nonnull final TextDecorator ownerInstance) {
                super(ownerInstance);
            }

            @Override
            public void handleMessage(final Message msg) {
                final TextDecorator owner = getOwnerInstance();
                if (owner == null) {
                    return;
                }
                switch (msg.what) {
                    case MSG_LAYOUT:
                        owner.layoutMain();
                        break;
                }
            }
        }

        /**
         * Puts a layout task into the scheduler. Does nothing if one or more layout tasks are
         * already scheduled.
         */
        public void invalidateLayout() {
            if (!mHandler.hasMessages(MSG_LAYOUT)) {
                mHandler.obtainMessage(MSG_LAYOUT).sendToTarget();
            }
        }

        /**
         * Clears the pending layout tasks.
         */
        public void cancelInvalidateLayout() {
            mHandler.removeMessages(MSG_LAYOUT);
        }
    }

    @Nonnull
    private final static Listener EMPTY_LISTENER = new Listener() {
        @Override
        public void onClickComposingTextToAddToDictionary(final String word) {
        }
    };

    @Nonnull
    private final static TextDecoratorUiOperator EMPTY_UI_OPERATOR = new TextDecoratorUiOperator() {
        @Override
        public void disposeUi() {
        }
        @Override
        public void hideUi() {
        }
        @Override
        public void setOnClickListener(Runnable listener) {
        }
        @Override
        public void layoutUi(Matrix matrix, RectF composingTextBounds, boolean useRtlLayout) {
        }
    };
}
+0 −262

File deleted.

Preview size limit exceeded, changes collapsed.

Loading