Introduce commit/add-to-dictionary indicators
This CL introduces commit/add-to-dictionary indicators. Note that the text is not yet highlighted when the commit indicator is displayed. It will be addressed in subsequent CLs. Change-Id: I7e9b0fcfdc0776a50a1d8cfb41ee0add813317ddmain
parent
8380f921f7
commit
bea17c49ec
|
@ -0,0 +1,127 @@
|
||||||
|
<?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 delay time in milliseconds from to show the commit indicator -->
|
||||||
|
<integer name="text_decorator_delay_in_milliseconds_to_show_commit_indicator">
|
||||||
|
500
|
||||||
|
</integer>
|
||||||
|
|
||||||
|
<!-- 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>
|
||||||
|
|
||||||
|
<!-- If true, the commit/add-to-text indicator will be suppressed when the word isn't going to
|
||||||
|
trigger auto-correction. -->
|
||||||
|
<bool name="text_decorator_only_for_auto_correction">false</bool>
|
||||||
|
|
||||||
|
<!-- If true, the commit/add-to-text indicator will be suppressed when the word is already in
|
||||||
|
the dictionary. -->
|
||||||
|
<bool name="text_decorator_only_for_out_of_vocabulary">false</bool>
|
||||||
|
|
||||||
|
<!-- Background color to be used to highlight the target text when the commit indicator is
|
||||||
|
visible. -->
|
||||||
|
<color name="text_decorator_commit_indicator_text_highlight_color">
|
||||||
|
#B6E2DE
|
||||||
|
</color>
|
||||||
|
|
||||||
|
<!-- Background color of the commit indicator. -->
|
||||||
|
<color name="text_decorator_commit_indicator_background_color">
|
||||||
|
#48B6AC
|
||||||
|
</color>
|
||||||
|
|
||||||
|
<!-- Foreground color of the commit indicator. -->
|
||||||
|
<color name="text_decorator_commit_indicator_foreground_color">
|
||||||
|
#FFFFFF
|
||||||
|
</color>
|
||||||
|
|
||||||
|
<!-- Viewport size of "text_decorator_commit_indicator_path". -->
|
||||||
|
<integer name="text_decorator_commit_indicator_path_size">
|
||||||
|
480
|
||||||
|
</integer>
|
||||||
|
|
||||||
|
<!-- Coordinates of the closed path to be used to render the commit indicator.
|
||||||
|
The format is: X[0], Y[0], X[1], Y[1], ..., X[N-1], Y[N-1] -->
|
||||||
|
<integer-array name="text_decorator_commit_indicator_path">
|
||||||
|
<item>180</item>
|
||||||
|
<item>323</item>
|
||||||
|
<item>97</item>
|
||||||
|
<item>240</item>
|
||||||
|
<item>68</item>
|
||||||
|
<item>268</item>
|
||||||
|
<item>180</item>
|
||||||
|
<item>380</item>
|
||||||
|
<item>420</item>
|
||||||
|
<item>140</item>
|
||||||
|
<item>392</item>
|
||||||
|
<item>112</item>
|
||||||
|
</integer-array>
|
||||||
|
|
||||||
|
<!-- 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,0 +1,425 @@
|
||||||
|
/*
|
||||||
|
* 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.PointF;
|
||||||
|
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.SuggestedWords.SuggestedWordInfo;
|
||||||
|
import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A controller class of commit/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 MODE_NONE = 0;
|
||||||
|
private static final int MODE_COMMIT = 1;
|
||||||
|
private static final int MODE_ADD_TO_DICTIONARY = 2;
|
||||||
|
|
||||||
|
private int mMode = MODE_NONE;
|
||||||
|
|
||||||
|
private final PointF mLocalOrigin = new PointF();
|
||||||
|
private final RectF mRelativeIndicatorBounds = new RectF();
|
||||||
|
private final RectF mRelativeComposingTextBounds = new RectF();
|
||||||
|
|
||||||
|
private boolean mIsFullScreenMode = false;
|
||||||
|
private SuggestedWordInfo mWaitingWord = null;
|
||||||
|
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 composing text to commit.
|
||||||
|
* @param wordInfo the suggested word which the user clicked on.
|
||||||
|
*/
|
||||||
|
void onClickComposingTextToCommit(final SuggestedWordInfo wordInfo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the user clicks the composing text to add the word into the dictionary.
|
||||||
|
* @param wordInfo the suggested word which the user clicked on.
|
||||||
|
*/
|
||||||
|
void onClickComposingTextToAddToDictionary(final SuggestedWordInfo wordInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextDecorator(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(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 "Commit" indicator and associates it with the given suggested word.
|
||||||
|
*
|
||||||
|
* <p>The effect of {@link #showCommitIndicator(SuggestedWordInfo)} and
|
||||||
|
* {@link #showAddToDictionaryIndicator(SuggestedWordInfo)} are exclusive to each other. Call
|
||||||
|
* {@link #reset()} to hide the indicator.</p>
|
||||||
|
*
|
||||||
|
* @param wordInfo the suggested word which should be associated with the indicator. This object
|
||||||
|
* will be passed back in {@link Listener#onClickComposingTextToCommit(SuggestedWordInfo)}
|
||||||
|
*/
|
||||||
|
public void showCommitIndicator(final SuggestedWordInfo wordInfo) {
|
||||||
|
if (mMode == MODE_COMMIT && wordInfo != null &&
|
||||||
|
TextUtils.equals(mWaitingWord.mWord, wordInfo.mWord)) {
|
||||||
|
// Skip layout for better performance.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mWaitingWord = wordInfo;
|
||||||
|
mMode = MODE_COMMIT;
|
||||||
|
layoutLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the "Add to dictionary" indicator and associates it with associating the given
|
||||||
|
* suggested word.
|
||||||
|
*
|
||||||
|
* <p>The effect of {@link #showCommitIndicator(SuggestedWordInfo)} and
|
||||||
|
* {@link #showAddToDictionaryIndicator(SuggestedWordInfo)} are exclusive to each other. Call
|
||||||
|
* {@link #reset()} to hide the indicator.</p>
|
||||||
|
*
|
||||||
|
* @param wordInfo the suggested word which should be associated with the indicator. This object
|
||||||
|
* will be passed back in
|
||||||
|
* {@link Listener#onClickComposingTextToAddToDictionary(SuggestedWordInfo)}.
|
||||||
|
*/
|
||||||
|
public void showAddToDictionaryIndicator(final SuggestedWordInfo wordInfo) {
|
||||||
|
if (mMode == MODE_ADD_TO_DICTIONARY && wordInfo != null &&
|
||||||
|
TextUtils.equals(mWaitingWord.mWord, wordInfo.mWord)) {
|
||||||
|
// Skip layout for better performance.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mWaitingWord = wordInfo;
|
||||||
|
mMode = MODE_ADD_TO_DICTIONARY;
|
||||||
|
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 currentFullScreenMode = mIsFullScreenMode;
|
||||||
|
if (!currentFullScreenMode && fullScreenMode) {
|
||||||
|
// Currently full screen mode is not supported.
|
||||||
|
// TODO: Support full screen mode.
|
||||||
|
hideIndicator();
|
||||||
|
}
|
||||||
|
mIsFullScreenMode = fullScreenMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets previous requests and makes indicator invisible.
|
||||||
|
*/
|
||||||
|
public void reset() {
|
||||||
|
mWaitingWord = null;
|
||||||
|
mMode = MODE_NONE;
|
||||||
|
mLocalOrigin.set(0.0f, 0.0f);
|
||||||
|
mRelativeIndicatorBounds.set(0.0f, 0.0f, 0.0f, 0.0f);
|
||||||
|
mRelativeComposingTextBounds.set(0.0f, 0.0f, 0.0f, 0.0f);
|
||||||
|
cancelLayoutInternalExpectedly("Resetting internal state.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Must be called when the {@link InputMethodService#onUpdateCursorAnchorInfo()} is called.
|
||||||
|
*
|
||||||
|
* <p>CAVEAT: Currently the input method author is responsible for ignoring
|
||||||
|
* {@link InputMethodService#onUpdateCursorAnchorInfo()} called in full screen mode.</p>
|
||||||
|
* @param info the compatibility wrapper object for the received {@link CursorAnchorInfo}.
|
||||||
|
*/
|
||||||
|
public void onUpdateCursorAnchorInfo(final CursorAnchorInfoCompatWrapper info) {
|
||||||
|
if (mIsFullScreenMode) {
|
||||||
|
// TODO: Consider to call InputConnection#requestCursorAnchorInfo to disable the
|
||||||
|
// event callback to suppress unnecessary event callbacks.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mCursorAnchorInfoWrapper = info;
|
||||||
|
// Do not use layoutLater() to minimize the latency.
|
||||||
|
layoutImmediately();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hideIndicator() {
|
||||||
|
mUiOperator.hideUi();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cancelLayoutInternalUnexpectedly(final String message) {
|
||||||
|
hideIndicator();
|
||||||
|
Log.d(TAG, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cancelLayoutInternalExpectedly(final String message) {
|
||||||
|
hideIndicator();
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void layoutLater() {
|
||||||
|
mLayoutInvalidator.invalidateLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void layoutImmediately() {
|
||||||
|
// Clear pending layout requests.
|
||||||
|
mLayoutInvalidator.cancelInvalidateLayout();
|
||||||
|
layoutMain();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void layoutMain() {
|
||||||
|
if (mIsFullScreenMode) {
|
||||||
|
cancelLayoutInternalUnexpectedly("Full screen mode isn't yet supported.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mMode != MODE_COMMIT && mMode != MODE_ADD_TO_DICTIONARY) {
|
||||||
|
if (mMode == MODE_NONE) {
|
||||||
|
cancelLayoutInternalExpectedly("Not ready for layouting.");
|
||||||
|
} else {
|
||||||
|
cancelLayoutInternalUnexpectedly("Unknown mMode=" + mMode);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (mMode == MODE_COMMIT) {
|
||||||
|
if (composingText == null) {
|
||||||
|
cancelLayoutInternalExpectedly("composingText is null.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final int composingTextStart = info.getComposingTextStart();
|
||||||
|
final int lastCharRectIndex = composingTextStart + composingText.length() - 1;
|
||||||
|
final RectF lastCharRect = info.getCharacterRect(lastCharRectIndex);
|
||||||
|
final int lastCharRectFlag = info.getCharacterRectFlags(lastCharRectIndex);
|
||||||
|
final int lastCharRectType =
|
||||||
|
lastCharRectFlag & CursorAnchorInfoCompatWrapper.CHARACTER_RECT_TYPE_MASK;
|
||||||
|
if (lastCharRect == null || matrix == null || lastCharRectType !=
|
||||||
|
CursorAnchorInfoCompatWrapper.CHARACTER_RECT_TYPE_FULLY_VISIBLE) {
|
||||||
|
hideIndicator();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final RectF segmentStartCharRect = new RectF(lastCharRect);
|
||||||
|
for (int i = composingText.length() - 2; i >= 0; --i) {
|
||||||
|
final RectF charRect = info.getCharacterRect(composingTextStart + i);
|
||||||
|
if (charRect == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (charRect.top != segmentStartCharRect.top) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (charRect.bottom != segmentStartCharRect.bottom) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
segmentStartCharRect.set(charRect);
|
||||||
|
}
|
||||||
|
|
||||||
|
mLocalOrigin.set(lastCharRect.right, lastCharRect.top);
|
||||||
|
mRelativeIndicatorBounds.set(lastCharRect.right, lastCharRect.top,
|
||||||
|
lastCharRect.right + lastCharRect.height(), lastCharRect.bottom);
|
||||||
|
mRelativeIndicatorBounds.offset(-mLocalOrigin.x, -mLocalOrigin.y);
|
||||||
|
|
||||||
|
mRelativeIndicatorBounds.set(lastCharRect.right, lastCharRect.top,
|
||||||
|
lastCharRect.right + lastCharRect.height(), lastCharRect.bottom);
|
||||||
|
mRelativeIndicatorBounds.offset(-mLocalOrigin.x, -mLocalOrigin.y);
|
||||||
|
|
||||||
|
mRelativeComposingTextBounds.set(segmentStartCharRect.left, segmentStartCharRect.top,
|
||||||
|
segmentStartCharRect.right, segmentStartCharRect.bottom);
|
||||||
|
mRelativeComposingTextBounds.offset(-mLocalOrigin.x, -mLocalOrigin.y);
|
||||||
|
|
||||||
|
if (mWaitingWord == null) {
|
||||||
|
cancelLayoutInternalExpectedly("mWaitingText is null.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (TextUtils.isEmpty(mWaitingWord.mWord)) {
|
||||||
|
cancelLayoutInternalExpectedly("mWaitingText.mWord is empty.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!TextUtils.equals(composingText, mWaitingWord.mWord)) {
|
||||||
|
// This is indeed an expected situation because of the asynchronous nature of
|
||||||
|
// input method framework in Android. Note that composingText is notified from the
|
||||||
|
// application, while mWaitingWord.mWord is obtained directly from the InputLogic.
|
||||||
|
cancelLayoutInternalExpectedly(
|
||||||
|
"Composing text doesn't match the one we are waiting for.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!TextUtils.isEmpty(composingText)) {
|
||||||
|
// This is an unexpected case.
|
||||||
|
// TODO: Document this.
|
||||||
|
hideIndicator();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// In MODE_ADD_TO_DICTIONARY, we cannot retrieve the character position at all because
|
||||||
|
// of the lack of composing text. We will use the insertion marker position instead.
|
||||||
|
if (info.isInsertionMarkerClipped()) {
|
||||||
|
hideIndicator();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final float insertionMarkerHolizontal = info.getInsertionMarkerHorizontal();
|
||||||
|
final float insertionMarkerTop = info.getInsertionMarkerTop();
|
||||||
|
mLocalOrigin.set(insertionMarkerHolizontal, insertionMarkerTop);
|
||||||
|
}
|
||||||
|
|
||||||
|
final RectF indicatorBounds = new RectF(mRelativeIndicatorBounds);
|
||||||
|
final RectF composingTextBounds = new RectF(mRelativeComposingTextBounds);
|
||||||
|
indicatorBounds.offset(mLocalOrigin.x, mLocalOrigin.y);
|
||||||
|
composingTextBounds.offset(mLocalOrigin.x, mLocalOrigin.y);
|
||||||
|
mUiOperator.layoutUi(mMode == MODE_COMMIT, matrix, indicatorBounds, composingTextBounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onClickIndicator() {
|
||||||
|
if (mWaitingWord == null || TextUtils.isEmpty(mWaitingWord.mWord)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (mMode) {
|
||||||
|
case MODE_COMMIT:
|
||||||
|
mListener.onClickComposingTextToCommit(mWaitingWord);
|
||||||
|
break;
|
||||||
|
case MODE_ADD_TO_DICTIONARY:
|
||||||
|
mListener.onClickComposingTextToAddToDictionary(mWaitingWord);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(final TextDecorator ownerInstance) {
|
||||||
|
mHandler = new HandlerImpl(ownerInstance);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int MSG_LAYOUT = 0;
|
||||||
|
|
||||||
|
private static final class HandlerImpl
|
||||||
|
extends LeakGuardHandlerWrapper<TextDecorator> {
|
||||||
|
public HandlerImpl(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final static Listener EMPTY_LISTENER = new Listener() {
|
||||||
|
@Override
|
||||||
|
public void onClickComposingTextToCommit(SuggestedWordInfo wordInfo) {
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onClickComposingTextToAddToDictionary(SuggestedWordInfo wordInfo) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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(boolean isCommitMode, Matrix matrix, RectF indicatorBounds,
|
||||||
|
RectF composingTextBounds) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,259 @@
|
||||||
|
/*
|
||||||
|
* 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.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Matrix;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Path;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.graphics.drawable.ColorDrawable;
|
||||||
|
import android.inputmethodservice.InputMethodService;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.ViewGroup.LayoutParams;
|
||||||
|
import android.view.ViewParent;
|
||||||
|
import android.widget.PopupWindow;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
|
|
||||||
|
import com.android.inputmethod.latin.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used as the UI component of {@link TextDecorator}.
|
||||||
|
*/
|
||||||
|
public final class TextDecoratorUi implements TextDecoratorUiOperator {
|
||||||
|
private static final boolean VISUAL_DEBUG = false;
|
||||||
|
private static final int VISUAL_DEBUG_HIT_AREA_COLOR = 0x80ff8000;
|
||||||
|
|
||||||
|
private final RelativeLayout mLocalRootView;
|
||||||
|
private final CommitIndicatorView mCommitIndicatorView;
|
||||||
|
private final AddToDictionaryIndicatorView mAddToDictionaryIndicatorView;
|
||||||
|
private final PopupWindow mTouchEventWindow;
|
||||||
|
private final View mTouchEventWindowClickListenerView;
|
||||||
|
private final float mHitAreaMarginInPixels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This constructor is designed to be called from {@link InputMethodService#setInputView(View)}.
|
||||||
|
* Other usages are not supported.
|
||||||
|
*
|
||||||
|
* @param context the context of the input method.
|
||||||
|
* @param inputView the view that is passed to {@link InputMethodService#setInputView(View)}.
|
||||||
|
*/
|
||||||
|
public TextDecoratorUi(final Context context, final View inputView) {
|
||||||
|
final Resources resources = context.getResources();
|
||||||
|
final int hitAreaMarginInDP = resources.getInteger(
|
||||||
|
R.integer.text_decorator_hit_area_margin_in_dp);
|
||||||
|
mHitAreaMarginInPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
|
||||||
|
hitAreaMarginInDP, resources.getDisplayMetrics());
|
||||||
|
|
||||||
|
mLocalRootView = new RelativeLayout(context);
|
||||||
|
mLocalRootView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
|
||||||
|
LayoutParams.MATCH_PARENT));
|
||||||
|
// TODO: Use #setBackground(null) for API Level >= 16.
|
||||||
|
mLocalRootView.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
|
||||||
|
|
||||||
|
final ViewGroup contentView = getContentView(inputView);
|
||||||
|
mCommitIndicatorView = new CommitIndicatorView(context);
|
||||||
|
mAddToDictionaryIndicatorView = new AddToDictionaryIndicatorView(context);
|
||||||
|
mLocalRootView.addView(mCommitIndicatorView);
|
||||||
|
mLocalRootView.addView(mAddToDictionaryIndicatorView);
|
||||||
|
if (contentView != null) {
|
||||||
|
contentView.addView(mLocalRootView);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This popup window is used to avoid the limitation that the input method is not able to
|
||||||
|
// observe the touch events happening outside of InputMethodService.Insets#touchableRegion.
|
||||||
|
// We don't use this popup window for rendering the UI for performance reasons though.
|
||||||
|
mTouchEventWindow = new PopupWindow(context);
|
||||||
|
if (VISUAL_DEBUG) {
|
||||||
|
mTouchEventWindow.setBackgroundDrawable(new ColorDrawable(VISUAL_DEBUG_HIT_AREA_COLOR));
|
||||||
|
} else {
|
||||||
|
mTouchEventWindow.setBackgroundDrawable(null);
|
||||||
|
}
|
||||||
|
mTouchEventWindowClickListenerView = new View(context);
|
||||||
|
mTouchEventWindow.setContentView(mTouchEventWindowClickListenerView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disposeUi() {
|
||||||
|
if (mLocalRootView != null) {
|
||||||
|
final ViewParent parent = mLocalRootView.getParent();
|
||||||
|
if (parent != null && parent instanceof ViewGroup) {
|
||||||
|
((ViewGroup) parent).removeView(mLocalRootView);
|
||||||
|
}
|
||||||
|
mLocalRootView.removeAllViews();
|
||||||
|
}
|
||||||
|
if (mTouchEventWindow != null) {
|
||||||
|
mTouchEventWindow.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void hideUi() {
|
||||||
|
mCommitIndicatorView.setVisibility(View.GONE);
|
||||||
|
mAddToDictionaryIndicatorView.setVisibility(View.GONE);
|
||||||
|
mTouchEventWindow.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void layoutUi(final boolean isCommitMode, final Matrix matrix,
|
||||||
|
final RectF indicatorBounds, final RectF composingTextBounds) {
|
||||||
|
final RectF indicatorBoundsInScreenCoordinates = new RectF();
|
||||||
|
matrix.mapRect(indicatorBoundsInScreenCoordinates, indicatorBounds);
|
||||||
|
mCommitIndicatorView.setBounds(indicatorBoundsInScreenCoordinates);
|
||||||
|
mAddToDictionaryIndicatorView.setBounds(indicatorBoundsInScreenCoordinates);
|
||||||
|
|
||||||
|
final RectF hitAreaBounds = new RectF(composingTextBounds);
|
||||||
|
hitAreaBounds.union(indicatorBounds);
|
||||||
|
final RectF hitAreaBoundsInScreenCoordinates = new RectF();
|
||||||
|
matrix.mapRect(hitAreaBoundsInScreenCoordinates, hitAreaBounds);
|
||||||
|
hitAreaBoundsInScreenCoordinates.inset(-mHitAreaMarginInPixels, -mHitAreaMarginInPixels);
|
||||||
|
|
||||||
|
final int[] originScreen = new int[2];
|
||||||
|
mLocalRootView.getLocationOnScreen(originScreen);
|
||||||
|
final int viewOriginX = originScreen[0];
|
||||||
|
final int viewOriginY = originScreen[1];
|
||||||
|
|
||||||
|
final View toBeShown;
|
||||||
|
final View toBeHidden;
|
||||||
|
if (isCommitMode) {
|
||||||
|
toBeShown = mCommitIndicatorView;
|
||||||
|
toBeHidden = mAddToDictionaryIndicatorView;
|
||||||
|
} else {
|
||||||
|
toBeShown = mAddToDictionaryIndicatorView;
|
||||||
|
toBeHidden = mCommitIndicatorView;
|
||||||
|
}
|
||||||
|
toBeShown.setX(indicatorBoundsInScreenCoordinates.left - viewOriginX);
|
||||||
|
toBeShown.setY(indicatorBoundsInScreenCoordinates.top - viewOriginY);
|
||||||
|
toBeShown.setVisibility(View.VISIBLE);
|
||||||
|
toBeHidden.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
if (mTouchEventWindow.isShowing()) {
|
||||||
|
mTouchEventWindow.update((int)hitAreaBoundsInScreenCoordinates.left - viewOriginX,
|
||||||
|
(int)hitAreaBoundsInScreenCoordinates.top - viewOriginY,
|
||||||
|
(int)hitAreaBoundsInScreenCoordinates.width(),
|
||||||
|
(int)hitAreaBoundsInScreenCoordinates.height());
|
||||||
|
} else {
|
||||||
|
mTouchEventWindow.setWidth((int)hitAreaBoundsInScreenCoordinates.width());
|
||||||
|
mTouchEventWindow.setHeight((int)hitAreaBoundsInScreenCoordinates.height());
|
||||||
|
mTouchEventWindow.showAtLocation(mLocalRootView, Gravity.NO_GRAVITY,
|
||||||
|
(int)hitAreaBoundsInScreenCoordinates.left - viewOriginX,
|
||||||
|
(int)hitAreaBoundsInScreenCoordinates.top - viewOriginY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOnClickListener(final Runnable listener) {
|
||||||
|
mTouchEventWindowClickListenerView.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(final View arg0) {
|
||||||
|
listener.run();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class IndicatorView extends View {
|
||||||
|
private final Path mPath;
|
||||||
|
private final Path mTmpPath = new Path();
|
||||||
|
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
private final Matrix mMatrix = new Matrix();
|
||||||
|
private final int mBackgroundColor;
|
||||||
|
private final int mForegroundColor;
|
||||||
|
private final RectF mBounds = new RectF();
|
||||||
|
public IndicatorView(Context context, final int pathResourceId,
|
||||||
|
final int sizeResourceId, final int backgroundColorResourceId,
|
||||||
|
final int foregroundColroResourceId) {
|
||||||
|
super(context);
|
||||||
|
final Resources resources = context.getResources();
|
||||||
|
mPath = createPath(resources, pathResourceId, sizeResourceId);
|
||||||
|
mBackgroundColor = resources.getColor(backgroundColorResourceId);
|
||||||
|
mForegroundColor = resources.getColor(foregroundColroResourceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBounds(final RectF rect) {
|
||||||
|
mBounds.set(rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDraw(Canvas canvas) {
|
||||||
|
mPaint.setColor(mBackgroundColor);
|
||||||
|
mPaint.setStyle(Paint.Style.FILL);
|
||||||
|
canvas.drawRect(0.0f, 0.0f, mBounds.width(), mBounds.height(), mPaint);
|
||||||
|
|
||||||
|
mMatrix.reset();
|
||||||
|
mMatrix.postScale(mBounds.width(), mBounds.height());
|
||||||
|
mPath.transform(mMatrix, mTmpPath);
|
||||||
|
mPaint.setColor(mForegroundColor);
|
||||||
|
canvas.drawPath(mTmpPath, mPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Path createPath(final Resources resources, final int pathResourceId,
|
||||||
|
final int sizeResourceId) {
|
||||||
|
final int size = resources.getInteger(sizeResourceId);
|
||||||
|
final float normalizationFactor = 1.0f / size;
|
||||||
|
final int[] array = resources.getIntArray(pathResourceId);
|
||||||
|
|
||||||
|
final Path path = new Path();
|
||||||
|
for (int i = 0; i < array.length; i += 2) {
|
||||||
|
if (i == 0) {
|
||||||
|
path.moveTo(array[i] * normalizationFactor, array[i + 1] * normalizationFactor);
|
||||||
|
} else {
|
||||||
|
path.lineTo(array[i] * normalizationFactor, array[i + 1] * normalizationFactor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
path.close();
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ViewGroup getContentView(final View view) {
|
||||||
|
final View rootView = view.getRootView();
|
||||||
|
if (rootView == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ViewGroup windowContentView = (ViewGroup)rootView.findViewById(android.R.id.content);
|
||||||
|
if (windowContentView == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return windowContentView;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class CommitIndicatorView extends TextDecoratorUi.IndicatorView {
|
||||||
|
public CommitIndicatorView(final Context context) {
|
||||||
|
super(context, R.array.text_decorator_commit_indicator_path,
|
||||||
|
R.integer.text_decorator_commit_indicator_path_size,
|
||||||
|
R.color.text_decorator_commit_indicator_background_color,
|
||||||
|
R.color.text_decorator_commit_indicator_foreground_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class AddToDictionaryIndicatorView extends TextDecoratorUi.IndicatorView {
|
||||||
|
public AddToDictionaryIndicatorView(final Context context) {
|
||||||
|
super(context, R.array.text_decorator_add_to_dictionary_indicator_path,
|
||||||
|
R.integer.text_decorator_add_to_dictionary_indicator_path_size,
|
||||||
|
R.color.text_decorator_add_to_dictionary_indicator_background_color,
|
||||||
|
R.color.text_decorator_add_to_dictionary_indicator_foreground_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* 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.PointF;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface defines how UI operations required for {@link TextDecorator} are delegated to
|
||||||
|
* the actual UI implementation class.
|
||||||
|
*/
|
||||||
|
public interface TextDecoratorUiOperator {
|
||||||
|
/**
|
||||||
|
* Called to notify that the UI is ready to be disposed.
|
||||||
|
*/
|
||||||
|
void disposeUi();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the UI should become invisible.
|
||||||
|
*/
|
||||||
|
void hideUi();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called to set the new click handler.
|
||||||
|
* @param onClickListener the callback object whose {@link Runnable#run()} should be called when
|
||||||
|
* the indicator is clicked.
|
||||||
|
*/
|
||||||
|
void setOnClickListener(final Runnable onClickListener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the layout should be updated.
|
||||||
|
* @param isCommitMode {@code true} if the commit indicator should be shown. Show the
|
||||||
|
* add-to-dictionary indicator otherwise.
|
||||||
|
* @param matrix The matrix that transforms the local coordinates into the screen coordinates.
|
||||||
|
* @param indicatorBounds The bounding box of the indicator, in local coordinates.
|
||||||
|
* @param composingTextBounds The bounding box of the composing text, in local coordinates.
|
||||||
|
*/
|
||||||
|
void layoutUi(final boolean isCommitMode, final Matrix matrix, final RectF indicatorBounds,
|
||||||
|
final RectF composingTextBounds);
|
||||||
|
}
|
|
@ -69,6 +69,7 @@ import com.android.inputmethod.keyboard.KeyboardActionListener;
|
||||||
import com.android.inputmethod.keyboard.KeyboardId;
|
import com.android.inputmethod.keyboard.KeyboardId;
|
||||||
import com.android.inputmethod.keyboard.KeyboardSwitcher;
|
import com.android.inputmethod.keyboard.KeyboardSwitcher;
|
||||||
import com.android.inputmethod.keyboard.MainKeyboardView;
|
import com.android.inputmethod.keyboard.MainKeyboardView;
|
||||||
|
import com.android.inputmethod.keyboard.TextDecoratorUi;
|
||||||
import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback;
|
import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback;
|
||||||
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
|
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
|
||||||
import com.android.inputmethod.latin.define.DebugFlags;
|
import com.android.inputmethod.latin.define.DebugFlags;
|
||||||
|
@ -183,8 +184,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
private static final int MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED = 6;
|
private static final int MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED = 6;
|
||||||
private static final int MSG_RESET_CACHES = 7;
|
private static final int MSG_RESET_CACHES = 7;
|
||||||
private static final int MSG_WAIT_FOR_DICTIONARY_LOAD = 8;
|
private static final int MSG_WAIT_FOR_DICTIONARY_LOAD = 8;
|
||||||
|
private static final int MSG_SHOW_COMMIT_INDICATOR = 9;
|
||||||
// Update this when adding new messages
|
// Update this when adding new messages
|
||||||
private static final int MSG_LAST = MSG_WAIT_FOR_DICTIONARY_LOAD;
|
private static final int MSG_LAST = MSG_SHOW_COMMIT_INDICATOR;
|
||||||
|
|
||||||
private static final int ARG1_NOT_GESTURE_INPUT = 0;
|
private static final int ARG1_NOT_GESTURE_INPUT = 0;
|
||||||
private static final int ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1;
|
private static final int ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1;
|
||||||
|
@ -195,6 +197,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
|
|
||||||
private int mDelayInMillisecondsToUpdateSuggestions;
|
private int mDelayInMillisecondsToUpdateSuggestions;
|
||||||
private int mDelayInMillisecondsToUpdateShiftState;
|
private int mDelayInMillisecondsToUpdateShiftState;
|
||||||
|
private int mDelayInMillisecondsToShowCommitIndicator;
|
||||||
|
|
||||||
public UIHandler(final LatinIME ownerInstance) {
|
public UIHandler(final LatinIME ownerInstance) {
|
||||||
super(ownerInstance);
|
super(ownerInstance);
|
||||||
|
@ -206,10 +209,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final Resources res = latinIme.getResources();
|
final Resources res = latinIme.getResources();
|
||||||
mDelayInMillisecondsToUpdateSuggestions =
|
mDelayInMillisecondsToUpdateSuggestions = res.getInteger(
|
||||||
res.getInteger(R.integer.config_delay_in_milliseconds_to_update_suggestions);
|
R.integer.config_delay_in_milliseconds_to_update_suggestions);
|
||||||
mDelayInMillisecondsToUpdateShiftState =
|
mDelayInMillisecondsToUpdateShiftState = res.getInteger(
|
||||||
res.getInteger(R.integer.config_delay_in_milliseconds_to_update_shift_state);
|
R.integer.config_delay_in_milliseconds_to_update_shift_state);
|
||||||
|
mDelayInMillisecondsToShowCommitIndicator = res.getInteger(
|
||||||
|
R.integer.text_decorator_delay_in_milliseconds_to_show_commit_indicator);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -258,7 +263,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
case MSG_RESET_CACHES:
|
case MSG_RESET_CACHES:
|
||||||
final SettingsValues settingsValues = latinIme.mSettings.getCurrent();
|
final SettingsValues settingsValues = latinIme.mSettings.getCurrent();
|
||||||
if (latinIme.mInputLogic.retryResetCachesAndReturnSuccess(
|
if (latinIme.mInputLogic.retryResetCachesAndReturnSuccess(
|
||||||
msg.arg1 == 1 /* tryResumeSuggestions */,
|
msg.arg1 == ARG1_TRUE /* tryResumeSuggestions */,
|
||||||
msg.arg2 /* remainingTries */, this /* handler */)) {
|
msg.arg2 /* remainingTries */, this /* handler */)) {
|
||||||
// If we were able to reset the caches, then we can reload the keyboard.
|
// If we were able to reset the caches, then we can reload the keyboard.
|
||||||
// Otherwise, we'll do it when we can.
|
// Otherwise, we'll do it when we can.
|
||||||
|
@ -267,6 +272,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
latinIme.getCurrentRecapitalizeState());
|
latinIme.getCurrentRecapitalizeState());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case MSG_SHOW_COMMIT_INDICATOR:
|
||||||
|
// Protocol of MSG_SET_COMMIT_INDICATOR_ENABLED:
|
||||||
|
// - what: MSG_SHOW_COMMIT_INDICATOR
|
||||||
|
// - arg1: not used.
|
||||||
|
// - arg2: not used.
|
||||||
|
// - obj: the Runnable object to be called back.
|
||||||
|
((Runnable) msg.obj).run();
|
||||||
|
break;
|
||||||
case MSG_WAIT_FOR_DICTIONARY_LOAD:
|
case MSG_WAIT_FOR_DICTIONARY_LOAD:
|
||||||
Log.i(TAG, "Timeout waiting for dictionary load");
|
Log.i(TAG, "Timeout waiting for dictionary load");
|
||||||
break;
|
break;
|
||||||
|
@ -367,6 +380,19 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
obtainMessage(MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED, suggestedWords).sendToTarget();
|
obtainMessage(MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED, suggestedWords).sendToTarget();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Posts a delayed task to show the commit indicator.
|
||||||
|
*
|
||||||
|
* <p>Only one task can exist in the queue. When this method is called, any prior task that
|
||||||
|
* has not yet fired will be canceled.</p>
|
||||||
|
* @param task the runnable object that will be fired when the delayed task is dispatched.
|
||||||
|
*/
|
||||||
|
public void postShowCommitIndicatorTask(final Runnable task) {
|
||||||
|
removeMessages(MSG_SHOW_COMMIT_INDICATOR);
|
||||||
|
sendMessageDelayed(obtainMessage(MSG_SHOW_COMMIT_INDICATOR, task),
|
||||||
|
mDelayInMillisecondsToShowCommitIndicator);
|
||||||
|
}
|
||||||
|
|
||||||
// Working variables for the following methods.
|
// Working variables for the following methods.
|
||||||
private boolean mIsOrientationChanging;
|
private boolean mIsOrientationChanging;
|
||||||
private boolean mPendingSuccessiveImsCallback;
|
private boolean mPendingSuccessiveImsCallback;
|
||||||
|
@ -717,6 +743,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
if (hasSuggestionStripView()) {
|
if (hasSuggestionStripView()) {
|
||||||
mSuggestionStripView.setListener(this, view);
|
mSuggestionStripView.setListener(this, view);
|
||||||
}
|
}
|
||||||
|
mInputLogic.setTextDecoratorUi(new TextDecoratorUi(this, view));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -972,9 +999,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
// @Override
|
// @Override
|
||||||
public void onUpdateCursorAnchorInfo(final CursorAnchorInfo info) {
|
public void onUpdateCursorAnchorInfo(final CursorAnchorInfo info) {
|
||||||
if (ProductionFlags.ENABLE_CURSOR_ANCHOR_INFO_CALLBACK) {
|
if (ProductionFlags.ENABLE_CURSOR_ANCHOR_INFO_CALLBACK) {
|
||||||
final CursorAnchorInfoCompatWrapper wrapper =
|
mInputLogic.onUpdateCursorAnchorInfo(CursorAnchorInfoCompatWrapper.fromObject(info));
|
||||||
CursorAnchorInfoCompatWrapper.fromObject(info);
|
|
||||||
// TODO: Implement here
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1178,6 +1203,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
// In fullscreen mode, no need to have extra space to show the key preview.
|
// In fullscreen mode, no need to have extra space to show the key preview.
|
||||||
// If not, we should have extra space above the keyboard to show the key preview.
|
// If not, we should have extra space above the keyboard to show the key preview.
|
||||||
mKeyPreviewBackingView.setVisibility(isFullscreenMode() ? View.GONE : View.VISIBLE);
|
mKeyPreviewBackingView.setVisibility(isFullscreenMode() ? View.GONE : View.VISIBLE);
|
||||||
|
mInputLogic.onUpdateFullscreenMode(isFullscreenMode());
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getCurrentAutoCapsState() {
|
private int getCurrentAutoCapsState() {
|
||||||
|
@ -1221,6 +1247,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
wordToEdit = word;
|
wordToEdit = word;
|
||||||
}
|
}
|
||||||
mDictionaryFacilitator.addWordToUserDictionary(this /* context */, wordToEdit);
|
mDictionaryFacilitator.addWordToUserDictionary(this /* context */, wordToEdit);
|
||||||
|
mInputLogic.onAddWordToUserDictionary();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callback for the {@link SuggestionStripView}, to call when the important notice strip is
|
// Callback for the {@link SuggestionStripView}, to call when the important notice strip is
|
||||||
|
@ -1409,7 +1436,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setSuggestedWords(final SuggestedWords suggestedWords) {
|
private void setSuggestedWords(final SuggestedWords suggestedWords) {
|
||||||
mInputLogic.setSuggestedWords(suggestedWords);
|
final SettingsValues currentSettingsValues = mSettings.getCurrent();
|
||||||
|
mInputLogic.setSuggestedWords(suggestedWords, currentSettingsValues, mHandler);
|
||||||
// TODO: Modify this when we support suggestions with hard keyboard
|
// TODO: Modify this when we support suggestions with hard keyboard
|
||||||
if (!hasSuggestionStripView()) {
|
if (!hasSuggestionStripView()) {
|
||||||
return;
|
return;
|
||||||
|
@ -1418,7 +1446,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final SettingsValues currentSettingsValues = mSettings.getCurrent();
|
|
||||||
final boolean shouldShowImportantNotice =
|
final boolean shouldShowImportantNotice =
|
||||||
ImportantNoticeUtils.shouldShowImportantNotice(this);
|
ImportantNoticeUtils.shouldShowImportantNotice(this);
|
||||||
final boolean shouldShowSuggestionCandidates =
|
final boolean shouldShowSuggestionCandidates =
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package com.android.inputmethod.latin.inputlogic;
|
package com.android.inputmethod.latin.inputlogic;
|
||||||
|
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
|
import android.inputmethodservice.InputMethodService;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
@ -27,11 +28,14 @@ import android.view.KeyEvent;
|
||||||
import android.view.inputmethod.CorrectionInfo;
|
import android.view.inputmethod.CorrectionInfo;
|
||||||
import android.view.inputmethod.EditorInfo;
|
import android.view.inputmethod.EditorInfo;
|
||||||
|
|
||||||
|
import com.android.inputmethod.compat.CursorAnchorInfoCompatWrapper;
|
||||||
import com.android.inputmethod.compat.SuggestionSpanUtils;
|
import com.android.inputmethod.compat.SuggestionSpanUtils;
|
||||||
import com.android.inputmethod.event.Event;
|
import com.android.inputmethod.event.Event;
|
||||||
import com.android.inputmethod.event.InputTransaction;
|
import com.android.inputmethod.event.InputTransaction;
|
||||||
import com.android.inputmethod.keyboard.KeyboardSwitcher;
|
import com.android.inputmethod.keyboard.KeyboardSwitcher;
|
||||||
import com.android.inputmethod.keyboard.ProximityInfo;
|
import com.android.inputmethod.keyboard.ProximityInfo;
|
||||||
|
import com.android.inputmethod.keyboard.TextDecorator;
|
||||||
|
import com.android.inputmethod.keyboard.TextDecoratorUiOperator;
|
||||||
import com.android.inputmethod.latin.Constants;
|
import com.android.inputmethod.latin.Constants;
|
||||||
import com.android.inputmethod.latin.Dictionary;
|
import com.android.inputmethod.latin.Dictionary;
|
||||||
import com.android.inputmethod.latin.DictionaryFacilitator;
|
import com.android.inputmethod.latin.DictionaryFacilitator;
|
||||||
|
@ -81,6 +85,18 @@ public final class InputLogic {
|
||||||
public final Suggest mSuggest;
|
public final Suggest mSuggest;
|
||||||
private final DictionaryFacilitator mDictionaryFacilitator;
|
private final DictionaryFacilitator mDictionaryFacilitator;
|
||||||
|
|
||||||
|
private final TextDecorator mTextDecorator = new TextDecorator(new TextDecorator.Listener() {
|
||||||
|
@Override
|
||||||
|
public void onClickComposingTextToCommit(SuggestedWordInfo wordInfo) {
|
||||||
|
mLatinIME.pickSuggestionManually(wordInfo);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onClickComposingTextToAddToDictionary(SuggestedWordInfo wordInfo) {
|
||||||
|
mLatinIME.addWordToUserDictionary(wordInfo.mWord);
|
||||||
|
mLatinIME.dismissAddToDictionaryHint();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
public LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
|
public LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
|
||||||
// This has package visibility so it can be accessed from InputLogicHandler.
|
// This has package visibility so it can be accessed from InputLogicHandler.
|
||||||
/* package */ final WordComposer mWordComposer;
|
/* package */ final WordComposer mWordComposer;
|
||||||
|
@ -303,8 +319,18 @@ public final class InputLogic {
|
||||||
return inputTransaction;
|
return inputTransaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
commitChosenWord(settingsValues, suggestion,
|
final boolean shouldShowAddToDictionaryHint = shouldShowAddToDictionaryHint(suggestionInfo);
|
||||||
LastComposedWord.COMMIT_TYPE_MANUAL_PICK, LastComposedWord.NOT_A_SEPARATOR);
|
final boolean shouldShowAddToDictionaryIndicator =
|
||||||
|
shouldShowAddToDictionaryHint && settingsValues.mShouldShowUiToAcceptTypedWord;
|
||||||
|
final int backgroundColor;
|
||||||
|
if (shouldShowAddToDictionaryIndicator) {
|
||||||
|
backgroundColor = settingsValues.mTextHighlightColorForAddToDictionaryIndicator;
|
||||||
|
} else {
|
||||||
|
backgroundColor = Color.TRANSPARENT;
|
||||||
|
}
|
||||||
|
commitChosenWordWithBackgroundColor(settingsValues, suggestion,
|
||||||
|
LastComposedWord.COMMIT_TYPE_MANUAL_PICK, LastComposedWord.NOT_A_SEPARATOR,
|
||||||
|
backgroundColor);
|
||||||
mConnection.endBatchEdit();
|
mConnection.endBatchEdit();
|
||||||
// Don't allow cancellation of manual pick
|
// Don't allow cancellation of manual pick
|
||||||
mLastComposedWord.deactivate();
|
mLastComposedWord.deactivate();
|
||||||
|
@ -312,13 +338,16 @@ public final class InputLogic {
|
||||||
mSpaceState = SpaceState.PHANTOM;
|
mSpaceState = SpaceState.PHANTOM;
|
||||||
inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
|
inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
|
||||||
|
|
||||||
if (shouldShowAddToDictionaryHint(suggestionInfo)) {
|
if (shouldShowAddToDictionaryHint) {
|
||||||
mSuggestionStripViewAccessor.showAddToDictionaryHint(suggestion);
|
mSuggestionStripViewAccessor.showAddToDictionaryHint(suggestion);
|
||||||
} else {
|
} else {
|
||||||
// If we're not showing the "Touch again to save", then update the suggestion strip.
|
// If we're not showing the "Touch again to save", then update the suggestion strip.
|
||||||
// That's going to be predictions (or punctuation suggestions), so INPUT_STYLE_NONE.
|
// That's going to be predictions (or punctuation suggestions), so INPUT_STYLE_NONE.
|
||||||
handler.postUpdateSuggestionStrip(SuggestedWords.INPUT_STYLE_NONE);
|
handler.postUpdateSuggestionStrip(SuggestedWords.INPUT_STYLE_NONE);
|
||||||
}
|
}
|
||||||
|
if (shouldShowAddToDictionaryIndicator) {
|
||||||
|
mTextDecorator.showAddToDictionaryIndicator(suggestionInfo);
|
||||||
|
}
|
||||||
return inputTransaction;
|
return inputTransaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -386,6 +415,8 @@ public final class InputLogic {
|
||||||
|
|
||||||
// The cursor has been moved : we now accept to perform recapitalization
|
// The cursor has been moved : we now accept to perform recapitalization
|
||||||
mRecapitalizeStatus.enable();
|
mRecapitalizeStatus.enable();
|
||||||
|
// We moved the cursor and need to invalidate the indicator right now.
|
||||||
|
mTextDecorator.reset();
|
||||||
// We moved the cursor. If we are touching a word, we need to resume suggestion.
|
// We moved the cursor. If we are touching a word, we need to resume suggestion.
|
||||||
mLatinIME.mHandler.postResumeSuggestions(false /* shouldIncludeResumedWordInSuggestions */,
|
mLatinIME.mHandler.postResumeSuggestions(false /* shouldIncludeResumedWordInSuggestions */,
|
||||||
true /* shouldDelay */);
|
true /* shouldDelay */);
|
||||||
|
@ -561,7 +592,8 @@ public final class InputLogic {
|
||||||
|
|
||||||
// TODO: on the long term, this method should become private, but it will be difficult.
|
// TODO: on the long term, this method should become private, but it will be difficult.
|
||||||
// Especially, how do we deal with InputMethodService.onDisplayCompletions?
|
// Especially, how do we deal with InputMethodService.onDisplayCompletions?
|
||||||
public void setSuggestedWords(final SuggestedWords suggestedWords) {
|
public void setSuggestedWords(final SuggestedWords suggestedWords,
|
||||||
|
final SettingsValues settingsValues, final LatinIME.UIHandler handler) {
|
||||||
if (SuggestedWords.EMPTY != suggestedWords) {
|
if (SuggestedWords.EMPTY != suggestedWords) {
|
||||||
final String autoCorrection;
|
final String autoCorrection;
|
||||||
if (suggestedWords.mWillAutoCorrect) {
|
if (suggestedWords.mWillAutoCorrect) {
|
||||||
|
@ -575,6 +607,38 @@ public final class InputLogic {
|
||||||
}
|
}
|
||||||
mSuggestedWords = suggestedWords;
|
mSuggestedWords = suggestedWords;
|
||||||
final boolean newAutoCorrectionIndicator = suggestedWords.mWillAutoCorrect;
|
final boolean newAutoCorrectionIndicator = suggestedWords.mWillAutoCorrect;
|
||||||
|
if (shouldShowCommitIndicator(suggestedWords, settingsValues)) {
|
||||||
|
// typedWordInfo is never null here.
|
||||||
|
final SuggestedWordInfo typedWordInfo = suggestedWords.getTypedWordInfoOrNull();
|
||||||
|
handler.postShowCommitIndicatorTask(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// TODO: This needs to be refactored to ensure that mWordComposer is accessed
|
||||||
|
// only from the UI thread.
|
||||||
|
if (!mWordComposer.isComposingWord()) {
|
||||||
|
mTextDecorator.reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final SuggestedWordInfo currentTypedWordInfo =
|
||||||
|
mSuggestedWords.getTypedWordInfoOrNull();
|
||||||
|
if (currentTypedWordInfo == null) {
|
||||||
|
mTextDecorator.reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!currentTypedWordInfo.equals(typedWordInfo)) {
|
||||||
|
// Suggested word has been changed. This task is obsolete.
|
||||||
|
mTextDecorator.reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mTextDecorator.showCommitIndicator(typedWordInfo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Note: It is OK to not cancel previous postShowCommitIndicatorTask() here. Having a
|
||||||
|
// cancellation mechanism could improve performance a bit though.
|
||||||
|
mTextDecorator.reset();
|
||||||
|
}
|
||||||
|
|
||||||
// Put a blue underline to a word in TextView which will be auto-corrected.
|
// Put a blue underline to a word in TextView which will be auto-corrected.
|
||||||
if (mIsAutoCorrectionIndicatorOn != newAutoCorrectionIndicator
|
if (mIsAutoCorrectionIndicatorOn != newAutoCorrectionIndicator
|
||||||
&& mWordComposer.isComposingWord()) {
|
&& mWordComposer.isComposingWord()) {
|
||||||
|
@ -756,6 +820,8 @@ public final class InputLogic {
|
||||||
if (!mWordComposer.isComposingWord() &&
|
if (!mWordComposer.isComposingWord() &&
|
||||||
mSuggestionStripViewAccessor.isShowingAddToDictionaryHint()) {
|
mSuggestionStripViewAccessor.isShowingAddToDictionaryHint()) {
|
||||||
mSuggestionStripViewAccessor.dismissAddToDictionaryHint();
|
mSuggestionStripViewAccessor.dismissAddToDictionaryHint();
|
||||||
|
mConnection.removeBackgroundColorFromHighlightedTextIfNecessary();
|
||||||
|
mTextDecorator.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
final int codePoint = event.mCodePoint;
|
final int codePoint = event.mCodePoint;
|
||||||
|
@ -2108,4 +2174,74 @@ public final class InputLogic {
|
||||||
settingsValues.mAutoCorrectionEnabledPerUserSettings,
|
settingsValues.mAutoCorrectionEnabledPerUserSettings,
|
||||||
inputStyle, sequenceNumber, callback);
|
inputStyle, sequenceNumber, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Following methods are tentatively placed in this class for the integration with
|
||||||
|
// TextDecorator.
|
||||||
|
// TODO: Decouple things that are not related to the input logic.
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the UI operator for {@link TextDecorator}.
|
||||||
|
* @param uiOperator the UI operator which should be associated with {@link TextDecorator}.
|
||||||
|
*/
|
||||||
|
public void setTextDecoratorUi(final TextDecoratorUiOperator uiOperator) {
|
||||||
|
mTextDecorator.setUiOperator(uiOperator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Must be called from {@link InputMethodService#onUpdateCursorAnchorInfo} is called.
|
||||||
|
* @param info The wrapper object with which we can access cursor/anchor info.
|
||||||
|
*/
|
||||||
|
public void onUpdateCursorAnchorInfo(final CursorAnchorInfoCompatWrapper info) {
|
||||||
|
mTextDecorator.onUpdateCursorAnchorInfo(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Must be called when {@link InputMethodService#updateFullscreenMode} is called.
|
||||||
|
* @param isFullscreen {@code true} if the input method is in full-screen mode.
|
||||||
|
*/
|
||||||
|
public void onUpdateFullscreenMode(final boolean isFullscreen) {
|
||||||
|
mTextDecorator.notifyFullScreenMode(isFullscreen);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Must be called from {@link LatinIME#addWordToUserDictionary(String)}.
|
||||||
|
*/
|
||||||
|
public void onAddWordToUserDictionary() {
|
||||||
|
mConnection.removeBackgroundColorFromHighlightedTextIfNecessary();
|
||||||
|
mTextDecorator.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the commit indicator should be shown or not.
|
||||||
|
* @param suggestedWords the suggested word that is being displayed.
|
||||||
|
* @param settingsValues the current settings value.
|
||||||
|
* @return {@code true} if the commit indicator should be shown.
|
||||||
|
*/
|
||||||
|
private boolean shouldShowCommitIndicator(final SuggestedWords suggestedWords,
|
||||||
|
final SettingsValues settingsValues) {
|
||||||
|
if (!settingsValues.mShouldShowUiToAcceptTypedWord) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final SuggestedWordInfo typedWordInfo = suggestedWords.getTypedWordInfoOrNull();
|
||||||
|
if (typedWordInfo == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (suggestedWords.mInputStyle != SuggestedWords.INPUT_STYLE_TYPING){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (settingsValues.mShowCommitIndicatorOnlyForAutoCorrection
|
||||||
|
&& !suggestedWords.mWillAutoCorrect) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// TODO: Calling shouldShowAddToDictionaryHint(typedWordInfo) multiple times should be fine
|
||||||
|
// in terms of performance, but we can do better. One idea is to make SuggestedWords include
|
||||||
|
// a boolean that tells whether the word is a dictionary word or not.
|
||||||
|
if (settingsValues.mShowCommitIndicatorOnlyForOutOfVocabulary
|
||||||
|
&& !shouldShowAddToDictionaryHint(typedWordInfo)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,6 +95,12 @@ public final class SettingsValues {
|
||||||
public final int[] mAdditionalFeaturesSettingValues =
|
public final int[] mAdditionalFeaturesSettingValues =
|
||||||
new int[AdditionalFeaturesSettingUtils.ADDITIONAL_FEATURES_SETTINGS_SIZE];
|
new int[AdditionalFeaturesSettingUtils.ADDITIONAL_FEATURES_SETTINGS_SIZE];
|
||||||
|
|
||||||
|
// TextDecorator
|
||||||
|
public final int mTextHighlightColorForCommitIndicator;
|
||||||
|
public final int mTextHighlightColorForAddToDictionaryIndicator;
|
||||||
|
public final boolean mShowCommitIndicatorOnlyForAutoCorrection;
|
||||||
|
public final boolean mShowCommitIndicatorOnlyForOutOfVocabulary;
|
||||||
|
|
||||||
// Debug settings
|
// Debug settings
|
||||||
public final boolean mIsInternal;
|
public final boolean mIsInternal;
|
||||||
public final int mKeyPreviewShowUpDuration;
|
public final int mKeyPreviewShowUpDuration;
|
||||||
|
@ -163,6 +169,14 @@ public final class SettingsValues {
|
||||||
mSuggestionsEnabledPerUserSettings = readSuggestionsEnabled(prefs);
|
mSuggestionsEnabledPerUserSettings = readSuggestionsEnabled(prefs);
|
||||||
AdditionalFeaturesSettingUtils.readAdditionalFeaturesPreferencesIntoArray(
|
AdditionalFeaturesSettingUtils.readAdditionalFeaturesPreferencesIntoArray(
|
||||||
prefs, mAdditionalFeaturesSettingValues);
|
prefs, mAdditionalFeaturesSettingValues);
|
||||||
|
mShowCommitIndicatorOnlyForAutoCorrection = res.getBoolean(
|
||||||
|
R.bool.text_decorator_only_for_auto_correction);
|
||||||
|
mShowCommitIndicatorOnlyForOutOfVocabulary = res.getBoolean(
|
||||||
|
R.bool.text_decorator_only_for_out_of_vocabulary);
|
||||||
|
mTextHighlightColorForCommitIndicator = res.getColor(
|
||||||
|
R.color.text_decorator_commit_indicator_text_highlight_color);
|
||||||
|
mTextHighlightColorForAddToDictionaryIndicator = res.getColor(
|
||||||
|
R.color.text_decorator_add_to_dictionary_indicator_text_highlight_color);
|
||||||
mIsInternal = Settings.isInternal(prefs);
|
mIsInternal = Settings.isInternal(prefs);
|
||||||
mKeyPreviewShowUpDuration = Settings.readKeyPreviewAnimationDuration(
|
mKeyPreviewShowUpDuration = Settings.readKeyPreviewAnimationDuration(
|
||||||
prefs, DebugSettings.PREF_KEY_PREVIEW_SHOW_UP_DURATION,
|
prefs, DebugSettings.PREF_KEY_PREVIEW_SHOW_UP_DURATION,
|
||||||
|
@ -396,6 +410,14 @@ public final class SettingsValues {
|
||||||
sb.append("" + (null == awu ? "null" : awu.toString()));
|
sb.append("" + (null == awu ? "null" : awu.toString()));
|
||||||
sb.append("\n mAdditionalFeaturesSettingValues = ");
|
sb.append("\n mAdditionalFeaturesSettingValues = ");
|
||||||
sb.append("" + Arrays.toString(mAdditionalFeaturesSettingValues));
|
sb.append("" + Arrays.toString(mAdditionalFeaturesSettingValues));
|
||||||
|
sb.append("\n mShowCommitIndicatorOnlyForAutoCorrection = ");
|
||||||
|
sb.append("" + mShowCommitIndicatorOnlyForAutoCorrection);
|
||||||
|
sb.append("\n mShowCommitIndicatorOnlyForOutOfVocabulary = ");
|
||||||
|
sb.append("" + mShowCommitIndicatorOnlyForOutOfVocabulary);
|
||||||
|
sb.append("\n mTextHighlightColorForCommitIndicator = ");
|
||||||
|
sb.append("" + mTextHighlightColorForCommitIndicator);
|
||||||
|
sb.append("\n mTextHighlightColorForAddToDictionaryIndicator = ");
|
||||||
|
sb.append("" + mTextHighlightColorForAddToDictionaryIndicator);
|
||||||
sb.append("\n mIsInternal = ");
|
sb.append("\n mIsInternal = ");
|
||||||
sb.append("" + mIsInternal);
|
sb.append("" + mIsInternal);
|
||||||
sb.append("\n mKeyPreviewShowUpDuration = ");
|
sb.append("\n mKeyPreviewShowUpDuration = ");
|
||||||
|
|
Loading…
Reference in New Issue