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

Commit bc3a2a97 authored by Yohei Yukawa's avatar Yohei Yukawa Committed by Android Git Automerger
Browse files

am 1b25b575: am b477d688: am d04271e7: am fa63d910: Merge "Use...

am 1b25b575: am b477d688: am d04271e7: am fa63d910: Merge "Use Add-To-Dictionary indicator only" into lmp-dev

* commit '1b25b575':
  Use Add-To-Dictionary indicator only
parents 7dc59d12 1b25b575
Loading
Loading
Loading
Loading
+0 −51
Original line number Diff line number Diff line
@@ -19,62 +19,11 @@
-->

<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">true</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">
+16 −2
Original line number Diff line number Diff line
@@ -41,6 +41,8 @@ public final class CursorAnchorInfoCompatWrapper {

    // Note that CursorAnchorInfo has been introduced in API level XX (Build.VERSION_CODE.LXX).
    private static final CompatUtils.ClassWrapper sCursorAnchorInfoClass;
    private static final CompatUtils.ToIntMethodWrapper sGetSelectionStartMethod;
    private static final CompatUtils.ToIntMethodWrapper sGetSelectionEndMethod;
    private static final CompatUtils.ToObjectMethodWrapper<RectF> sGetCharacterBoundsMethod;
    private static final CompatUtils.ToIntMethodWrapper sGetCharacterBoundsFlagsMethod;
    private static final CompatUtils.ToObjectMethodWrapper<CharSequence> sGetComposingTextMethod;
@@ -52,10 +54,14 @@ public final class CursorAnchorInfoCompatWrapper {
    private static final CompatUtils.ToObjectMethodWrapper<Matrix> sGetMatrixMethod;
    private static final CompatUtils.ToIntMethodWrapper sGetInsertionMarkerFlagsMethod;

    private static int COMPOSING_TEXT_START_DEFAULT = -1;
    private static int INVALID_TEXT_INDEX = -1;
    static {
        sCursorAnchorInfoClass = CompatUtils.getClassWrapper(
                "android.view.inputmethod.CursorAnchorInfo");
        sGetSelectionStartMethod = sCursorAnchorInfoClass.getPrimitiveMethod(
                "getSelectionStart", INVALID_TEXT_INDEX);
        sGetSelectionEndMethod = sCursorAnchorInfoClass.getPrimitiveMethod(
                "getSelectionEnd", INVALID_TEXT_INDEX);
        sGetCharacterBoundsMethod = sCursorAnchorInfoClass.getMethod(
                "getCharacterBounds", (RectF)null, int.class);
        sGetCharacterBoundsFlagsMethod = sCursorAnchorInfoClass.getPrimitiveMethod(
@@ -63,7 +69,7 @@ public final class CursorAnchorInfoCompatWrapper {
        sGetComposingTextMethod = sCursorAnchorInfoClass.getMethod(
                "getComposingText", (CharSequence)null);
        sGetComposingTextStartMethod = sCursorAnchorInfoClass.getPrimitiveMethod(
                "getComposingTextStart", COMPOSING_TEXT_START_DEFAULT);
                "getComposingTextStart", INVALID_TEXT_INDEX);
        sGetInsertionMarkerBaselineMethod = sCursorAnchorInfoClass.getPrimitiveMethod(
                "getInsertionMarkerBaseline", 0.0f);
        sGetInsertionMarkerBottomMethod = sCursorAnchorInfoClass.getPrimitiveMethod(
@@ -105,6 +111,14 @@ public final class CursorAnchorInfoCompatWrapper {
        return FakeHolder.sInstance;
    }

    public int getSelectionStart() {
        return sGetSelectionStartMethod.invoke(mInstance);
    }

    public int getSelectionEnd() {
        return sGetSelectionEndMethod.invoke(mInstance);
    }

    public CharSequence getComposingText() {
        return sGetComposingTextMethod.invoke(mInstance);
    }
+121 −173
Original line number Diff line number Diff line
@@ -17,23 +17,22 @@
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
 * 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}.
 */
@@ -41,18 +40,22 @@ 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 static final int INVALID_CURSOR_INDEX = -1;

    private int mMode = MODE_NONE;
    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 final PointF mLocalOrigin = new PointF();
    private final RectF mRelativeIndicatorBounds = new RectF();
    private final RectF mRelativeComposingTextBounds = new RectF();
    private int mMode = MODE_MONITOR;

    private String mLastComposingText = null;
    private RectF mIndicatorBoundsForLastComposingText = new RectF();
    private RectF mComposingTextBoundsForLastComposingText = new RectF();

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

    @Nonnull
@@ -63,16 +66,10 @@ public class TextDecorator {

    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.
         * 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 SuggestedWordInfo wordInfo);
        void onClickComposingTextToAddToDictionary(final String word);
    }

    public TextDecorator(final Listener listener) {
@@ -103,46 +100,19 @@ public class TextDecorator {
    }

    /**
     * Shows the "Commit" indicator and associates it with the given suggested word.
     * Shows the "Add to dictionary" indicator and associates it with associating the given 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)}
     * @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 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;
    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;
    }
@@ -165,18 +135,19 @@ public class TextDecorator {
     */
    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);
        mMode = MODE_MONITOR;
        mWaitingCursorStart = INVALID_CURSOR_INDEX;
        mWaitingCursorEnd = INVALID_CURSOR_INDEX;
        cancelLayoutInternalExpectedly("Resetting internal state.");
    }

    /**
     * Must be called when the {@link InputMethodService#onUpdateCursorAnchorInfo()} is called.
     * 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()} called in full screen mode.</p>
     * {@link InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)} called in full screen
     * mode.</p>
     * @param info the compatibility wrapper object for the received {@link CursorAnchorInfo}.
     */
    public void onUpdateCursorAnchorInfo(final CursorAnchorInfoCompatWrapper info) {
@@ -185,29 +156,6 @@ public class TextDecorator {
        layoutImmediately();
    }

    /**
     * Hides indicator if the new composing text doesn't match the expected one.
     *
     * <p>Calling this method is optional but recommended whenever the new composition is passed to
     * the application. The motivation of this method is to reduce the UI latency. With this method,
     * we can hide the indicator without waiting the arrival of the
     * {@link InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)} callback, assuming that
     * the application accepts the new composing text without any modification. Even if this
     * assumption is false, the indicator will be shown again when
     * {@link InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)} is actually received.
     * </p>
     *
     * @param newComposingText the new composing text that is being passed to the application.
     */
    public void hideIndicatorIfNecessary(final CharSequence newComposingText) {
        if (mMode != MODE_COMMIT && mMode != MODE_ADD_TO_DICTIONARY) {
            return;
        }
        if (!TextUtils.equals(newComposingText, mWaitingWord.mWord)) {
            mUiOperator.hideUi();
        }
    }

    private void cancelLayoutInternalUnexpectedly(final String message) {
        mUiOperator.hideUi();
        Log.d(TAG, message);
@@ -232,15 +180,6 @@ public class TextDecorator {
    }

    private void layoutMain() {
        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) {
@@ -254,104 +193,117 @@ public class TextDecorator {
        }

        final CharSequence composingText = info.getComposingText();
        if (mMode == MODE_COMMIT) {
            if (composingText == null) {
                cancelLayoutInternalExpectedly("composingText is null.");
                return;
            }
        if (!TextUtils.isEmpty(composingText)) {
            final int composingTextStart = info.getComposingTextStart();
            final int lastCharRectIndex = composingTextStart + composingText.length() - 1;
            final RectF lastCharRect = info.getCharacterBounds(lastCharRectIndex);
            final int lastCharRectFlag = info.getCharacterBoundsFlags(lastCharRectIndex);
            final int lastCharRectFlags = info.getCharacterBoundsFlags(lastCharRectIndex);
            final boolean hasInvisibleRegionInLastCharRect =
                    (lastCharRectFlag & CursorAnchorInfoCompatWrapper.FLAG_HAS_INVISIBLE_REGION)
                    (lastCharRectFlags & CursorAnchorInfoCompatWrapper.FLAG_HAS_INVISIBLE_REGION)
                            != 0;
            if (lastCharRect == null || matrix == null || hasInvisibleRegionInLastCharRect) {
                mUiOperator.hideUi();
                return;
            }
            final RectF segmentStartCharRect = new RectF(lastCharRect);
            for (int i = composingText.length() - 2; i >= 0; --i) {
                final RectF charRect = info.getCharacterBounds(composingTextStart + i);
                if (charRect == null) {

            // 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 (charRect.top != segmentStartCharRect.top) {
                if (characterBounds.top != top) {
                    break;
                }
                if (charRect.bottom != segmentStartCharRect.bottom) {
                if (characterBounds.bottom != bottom) {
                    break;
                }
                segmentStartCharRect.set(charRect);
                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;
            mComposingTextBoundsForLastComposingText.set(left, top, right, bottom);
            // The height and width of the indicator is the same as the height of the composing
            // text.
            final float indicatorSize = bottom - top;
            mIndicatorBoundsForLastComposingText.set(0.0f, 0.0f, indicatorSize, indicatorSize);
            // The horizontal position of the indicator depends on the text direction.
            final float indicatorTop = top;
            final float indicatorLeft;
            if (useRtlLayout) {
                indicatorLeft = left - indicatorSize;
            } else {
                indicatorLeft = right;
            }
            mIndicatorBoundsForLastComposingText.offset(indicatorLeft, indicatorTop);
        }

            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.");
        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;
                }
            if (TextUtils.isEmpty(mWaitingWord.mWord)) {
                cancelLayoutInternalExpectedly("mWaitingText.mWord is empty.");
                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;
                }
            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.");
                break;
            default:
                cancelLayoutInternalUnexpectedly("Unexpected internal mode=" + mMode);
                return;
        }
        } else {
            if (!mIsFullScreenMode && !TextUtils.isEmpty(composingText)) {
                // This is an unexpected case.
                // TODO: Document this.
                mUiOperator.hideUi();

        if (!TextUtils.equals(mLastComposingText, mWaitingWord)) {
            cancelLayoutInternalUnexpectedly("mLastComposingText doesn't match mWaitingWord");
            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.getInsertionMarkerFlags() &
                CursorAnchorInfoCompatWrapper.FLAG_HAS_INVISIBLE_REGION) != 0) {
            mUiOperator.hideUi();
            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);
        mUiOperator.layoutUi(matrix, mIndicatorBoundsForLastComposingText,
                mComposingTextBoundsForLastComposingText);
    }

    private void onClickIndicator() {
        if (mWaitingWord == null || TextUtils.isEmpty(mWaitingWord.mWord)) {
        if (mMode != MODE_SHOWING_INDICATOR) {
            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);
@@ -407,10 +359,7 @@ public class TextDecorator {

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

@@ -425,8 +374,7 @@ public class TextDecorator {
        public void setOnClickListener(Runnable listener) {
        }
        @Override
        public void layoutUi(boolean isCommitMode, Matrix matrix, RectF indicatorBounds,
                RectF composingTextBounds) {
        public void layoutUi(Matrix matrix, RectF indicatorBounds, RectF composingTextBounds) {
        }
    };
}
+5 −30

File changed.

Preview size limit exceeded, changes collapsed.

+1 −3
Original line number Diff line number Diff line
@@ -44,12 +44,10 @@ public interface TextDecoratorUiOperator {

    /**
     * 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,
    void layoutUi(final Matrix matrix, final RectF indicatorBounds,
            final RectF composingTextBounds);
}
Loading