131a49efe2adb59e31611f6871895a3243d835127Yuichi Araki/*
231a49efe2adb59e31611f6871895a3243d835127Yuichi Araki * Copyright (C) 2015 The Android Open Source Project
331a49efe2adb59e31611f6871895a3243d835127Yuichi Araki *
431a49efe2adb59e31611f6871895a3243d835127Yuichi Araki * Licensed under the Apache License, Version 2.0 (the "License");
531a49efe2adb59e31611f6871895a3243d835127Yuichi Araki * you may not use this file except in compliance with the License.
631a49efe2adb59e31611f6871895a3243d835127Yuichi Araki * You may obtain a copy of the License at
731a49efe2adb59e31611f6871895a3243d835127Yuichi Araki *
831a49efe2adb59e31611f6871895a3243d835127Yuichi Araki *      http://www.apache.org/licenses/LICENSE-2.0
931a49efe2adb59e31611f6871895a3243d835127Yuichi Araki *
1031a49efe2adb59e31611f6871895a3243d835127Yuichi Araki * Unless required by applicable law or agreed to in writing, software
1131a49efe2adb59e31611f6871895a3243d835127Yuichi Araki * distributed under the License is distributed on an "AS IS" BASIS,
1231a49efe2adb59e31611f6871895a3243d835127Yuichi Araki * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1331a49efe2adb59e31611f6871895a3243d835127Yuichi Araki * See the License for the specific language governing permissions and
1431a49efe2adb59e31611f6871895a3243d835127Yuichi Araki * limitations under the License.
1531a49efe2adb59e31611f6871895a3243d835127Yuichi Araki */
1631a49efe2adb59e31611f6871895a3243d835127Yuichi Araki
17ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikaspackage androidx.core.widget;
1831a49efe2adb59e31611f6871895a3243d835127Yuichi Araki
19ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
20cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu
2169decef5d945e46fcebfc3ee33ced2070987bf70Jake Whartonimport android.annotation.TargetApi;
22957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popaimport android.app.Activity;
23957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popaimport android.content.Context;
24957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popaimport android.content.Intent;
25957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popaimport android.content.pm.PackageManager;
26957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popaimport android.content.pm.ResolveInfo;
27329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popaimport android.graphics.Paint;
2831a49efe2adb59e31611f6871895a3243d835127Yuichi Arakiimport android.graphics.drawable.Drawable;
2931a49efe2adb59e31611f6871895a3243d835127Yuichi Arakiimport android.os.Build;
30957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popaimport android.text.Editable;
31744f0b5067100b2f32d970e213cad0cc61602adcAurimas Liutikasimport android.util.Log;
32cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanuimport android.util.TypedValue;
33957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popaimport android.view.ActionMode;
34957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popaimport android.view.Menu;
35957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popaimport android.view.MenuItem;
36744f0b5067100b2f32d970e213cad0cc61602adcAurimas Liutikasimport android.view.View;
3731a49efe2adb59e31611f6871895a3243d835127Yuichi Arakiimport android.widget.TextView;
3831a49efe2adb59e31611f6871895a3243d835127Yuichi Araki
399dede51868bbbe16aadcd65e04860bea8ea50e05Aurimas Liutikasimport androidx.annotation.DrawableRes;
409dede51868bbbe16aadcd65e04860bea8ea50e05Aurimas Liutikasimport androidx.annotation.IntDef;
41329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popaimport androidx.annotation.IntRange;
429dede51868bbbe16aadcd65e04860bea8ea50e05Aurimas Liutikasimport androidx.annotation.NonNull;
439dede51868bbbe16aadcd65e04860bea8ea50e05Aurimas Liutikasimport androidx.annotation.Nullable;
44329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popaimport androidx.annotation.Px;
459dede51868bbbe16aadcd65e04860bea8ea50e05Aurimas Liutikasimport androidx.annotation.RestrictTo;
469dede51868bbbe16aadcd65e04860bea8ea50e05Aurimas Liutikasimport androidx.annotation.StyleRes;
47329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popaimport androidx.core.os.BuildCompat;
48329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popaimport androidx.core.util.Preconditions;
499dede51868bbbe16aadcd65e04860bea8ea50e05Aurimas Liutikas
50cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanuimport java.lang.annotation.Retention;
51cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanuimport java.lang.annotation.RetentionPolicy;
52744f0b5067100b2f32d970e213cad0cc61602adcAurimas Liutikasimport java.lang.reflect.Field;
53957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popaimport java.lang.reflect.InvocationTargetException;
54957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popaimport java.lang.reflect.Method;
55957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popaimport java.util.ArrayList;
56957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popaimport java.util.List;
57744f0b5067100b2f32d970e213cad0cc61602adcAurimas Liutikas
5831a49efe2adb59e31611f6871895a3243d835127Yuichi Araki/**
59645e5c8aa6b31961c5f73f3d30bb5261d5e04aebKirill Grouchnikov * Helper for accessing features in {@link TextView}.
6031a49efe2adb59e31611f6871895a3243d835127Yuichi Araki */
61c5847d13e40f5d52459f5c0dab32dc08f1a9a683Chris Banespublic final class TextViewCompat {
625a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton    private static final String LOG_TAG = "TextViewCompat";
6331a49efe2adb59e31611f6871895a3243d835127Yuichi Araki
64cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu    /**
65cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * The TextView does not auto-size text (default).
66cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     */
67a500c93793f2775423d93efd8b81bbe7e1d7c0efAurimas Liutikas    public static final int AUTO_SIZE_TEXT_TYPE_NONE = TextView.AUTO_SIZE_TEXT_TYPE_NONE;
68cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu
69cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu    /**
70cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * The TextView scales text size both horizontally and vertically to fit within the
71cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * container.
72cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     */
73a500c93793f2775423d93efd8b81bbe7e1d7c0efAurimas Liutikas    public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = TextView.AUTO_SIZE_TEXT_TYPE_UNIFORM;
74cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu
75cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu    /** @hide */
76cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu    @RestrictTo(LIBRARY_GROUP)
77cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu    @IntDef({AUTO_SIZE_TEXT_TYPE_NONE, AUTO_SIZE_TEXT_TYPE_UNIFORM})
78cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu    @Retention(RetentionPolicy.SOURCE)
79cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu    public @interface AutoSizeTextType {}
80cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu
815a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton    private static Field sMaximumField;
825a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton    private static boolean sMaximumFieldFetched;
835a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton    private static Field sMaxModeField;
845a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton    private static boolean sMaxModeFieldFetched;
8531a49efe2adb59e31611f6871895a3243d835127Yuichi Araki
865a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton    private static Field sMinimumField;
875a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton    private static boolean sMinimumFieldFetched;
885a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton    private static Field sMinModeField;
895a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton    private static boolean sMinModeFieldFetched;
9031a49efe2adb59e31611f6871895a3243d835127Yuichi Araki
915a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton    private static final int LINES = 1;
9231a49efe2adb59e31611f6871895a3243d835127Yuichi Araki
935a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton    // Hide constructor
945a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton    private TextViewCompat() {}
95744f0b5067100b2f32d970e213cad0cc61602adcAurimas Liutikas
965a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton    private static Field retrieveField(String fieldName) {
975a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        Field field = null;
985a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        try {
995a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            field = TextView.class.getDeclaredField(fieldName);
1005a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            field.setAccessible(true);
1015a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        } catch (NoSuchFieldException e) {
1025a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            Log.e(LOG_TAG, "Could not retrieve " + fieldName + " field.");
103744f0b5067100b2f32d970e213cad0cc61602adcAurimas Liutikas        }
1045a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        return field;
1055a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton    }
106744f0b5067100b2f32d970e213cad0cc61602adcAurimas Liutikas
1075a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton    private static int retrieveIntFromField(Field field, TextView textView) {
1085a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        try {
1095a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            return field.getInt(textView);
1105a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        } catch (IllegalAccessException e) {
1115a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            Log.d(LOG_TAG, "Could not retrieve value of " + field.getName() + " field.");
1120bd3435d26a1c5daa0d205324c327dee4992bfbeKirill Grouchnikov        }
1135a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        return -1;
1145a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton    }
115ddf46923b9487ac3a021f01b55d96136d811e8beChris Banes
11631a49efe2adb59e31611f6871895a3243d835127Yuichi Araki    /**
11731a49efe2adb59e31611f6871895a3243d835127Yuichi Araki     * Sets the Drawables (if any) to appear to the start of, above, to the end
11831a49efe2adb59e31611f6871895a3243d835127Yuichi Araki     * of, and below the text. Use {@code null} if you do not want a Drawable
11931a49efe2adb59e31611f6871895a3243d835127Yuichi Araki     * there. The Drawables must already have had {@link Drawable#setBounds}
12031a49efe2adb59e31611f6871895a3243d835127Yuichi Araki     * called.
12131a49efe2adb59e31611f6871895a3243d835127Yuichi Araki     * <p/>
12231a49efe2adb59e31611f6871895a3243d835127Yuichi Araki     * Calling this method will overwrite any Drawables previously set using
12331a49efe2adb59e31611f6871895a3243d835127Yuichi Araki     * {@link TextView#setCompoundDrawables} or related methods.
12431a49efe2adb59e31611f6871895a3243d835127Yuichi Araki     *
12531a49efe2adb59e31611f6871895a3243d835127Yuichi Araki     * @param textView The TextView against which to invoke the method.
126929f27aab7ac7231f3734c988d5ee7201627d535Alan Viverette     * @attr name android:drawableStart
127929f27aab7ac7231f3734c988d5ee7201627d535Alan Viverette     * @attr name android:drawableTop
128929f27aab7ac7231f3734c988d5ee7201627d535Alan Viverette     * @attr name android:drawableEnd
129929f27aab7ac7231f3734c988d5ee7201627d535Alan Viverette     * @attr name android:drawableBottom
13031a49efe2adb59e31611f6871895a3243d835127Yuichi Araki     */
13131a49efe2adb59e31611f6871895a3243d835127Yuichi Araki    public static void setCompoundDrawablesRelative(@NonNull TextView textView,
13231a49efe2adb59e31611f6871895a3243d835127Yuichi Araki            @Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end,
13331a49efe2adb59e31611f6871895a3243d835127Yuichi Araki            @Nullable Drawable bottom) {
1345a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        if (Build.VERSION.SDK_INT >= 18) {
1355a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            textView.setCompoundDrawablesRelative(start, top, end, bottom);
1365a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        } else if (Build.VERSION.SDK_INT >= 17) {
1375a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            boolean rtl = textView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
1385a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            textView.setCompoundDrawables(rtl ? end : start, top, rtl ? start : end, bottom);
1395a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        } else {
1405a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            textView.setCompoundDrawables(start, top, end, bottom);
1415a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        }
14231a49efe2adb59e31611f6871895a3243d835127Yuichi Araki    }
14331a49efe2adb59e31611f6871895a3243d835127Yuichi Araki
14431a49efe2adb59e31611f6871895a3243d835127Yuichi Araki    /**
14531a49efe2adb59e31611f6871895a3243d835127Yuichi Araki     * Sets the Drawables (if any) to appear to the start of, above, to the end
14631a49efe2adb59e31611f6871895a3243d835127Yuichi Araki     * of, and below the text. Use {@code null} if you do not want a Drawable
14731a49efe2adb59e31611f6871895a3243d835127Yuichi Araki     * there. The Drawables' bounds will be set to their intrinsic bounds.
14831a49efe2adb59e31611f6871895a3243d835127Yuichi Araki     * <p/>
14931a49efe2adb59e31611f6871895a3243d835127Yuichi Araki     * Calling this method will overwrite any Drawables previously set using
15031a49efe2adb59e31611f6871895a3243d835127Yuichi Araki     * {@link TextView#setCompoundDrawables} or related methods.
15131a49efe2adb59e31611f6871895a3243d835127Yuichi Araki     *
15231a49efe2adb59e31611f6871895a3243d835127Yuichi Araki     * @param textView The TextView against which to invoke the method.
153929f27aab7ac7231f3734c988d5ee7201627d535Alan Viverette     * @attr name android:drawableStart
154929f27aab7ac7231f3734c988d5ee7201627d535Alan Viverette     * @attr name android:drawableTop
155929f27aab7ac7231f3734c988d5ee7201627d535Alan Viverette     * @attr name android:drawableEnd
156929f27aab7ac7231f3734c988d5ee7201627d535Alan Viverette     * @attr name android:drawableBottom
15731a49efe2adb59e31611f6871895a3243d835127Yuichi Araki     */
15831a49efe2adb59e31611f6871895a3243d835127Yuichi Araki    public static void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
15931a49efe2adb59e31611f6871895a3243d835127Yuichi Araki            @Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end,
16031a49efe2adb59e31611f6871895a3243d835127Yuichi Araki            @Nullable Drawable bottom) {
1615a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        if (Build.VERSION.SDK_INT >= 18) {
1625a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            textView.setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom);
1635a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        } else if (Build.VERSION.SDK_INT >= 17) {
1645a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            boolean rtl = textView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
1655a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            textView.setCompoundDrawablesWithIntrinsicBounds(rtl ? end : start, top,
1665a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton                    rtl ? start : end,  bottom);
1675a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        } else {
1685a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            textView.setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom);
1695a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        }
17031a49efe2adb59e31611f6871895a3243d835127Yuichi Araki    }
17131a49efe2adb59e31611f6871895a3243d835127Yuichi Araki
17231a49efe2adb59e31611f6871895a3243d835127Yuichi Araki    /**
17331a49efe2adb59e31611f6871895a3243d835127Yuichi Araki     * Sets the Drawables (if any) to appear to the start of, above, to the end
17431a49efe2adb59e31611f6871895a3243d835127Yuichi Araki     * of, and below the text. Use 0 if you do not want a Drawable there. The
17531a49efe2adb59e31611f6871895a3243d835127Yuichi Araki     * Drawables' bounds will be set to their intrinsic bounds.
17631a49efe2adb59e31611f6871895a3243d835127Yuichi Araki     * <p/>
17731a49efe2adb59e31611f6871895a3243d835127Yuichi Araki     * Calling this method will overwrite any Drawables previously set using
17831a49efe2adb59e31611f6871895a3243d835127Yuichi Araki     * {@link TextView#setCompoundDrawables} or related methods.
17931a49efe2adb59e31611f6871895a3243d835127Yuichi Araki     *
18031a49efe2adb59e31611f6871895a3243d835127Yuichi Araki     * @param textView The TextView against which to invoke the method.
18131a49efe2adb59e31611f6871895a3243d835127Yuichi Araki     * @param start    Resource identifier of the start Drawable.
18231a49efe2adb59e31611f6871895a3243d835127Yuichi Araki     * @param top      Resource identifier of the top Drawable.
18331a49efe2adb59e31611f6871895a3243d835127Yuichi Araki     * @param end      Resource identifier of the end Drawable.
18431a49efe2adb59e31611f6871895a3243d835127Yuichi Araki     * @param bottom   Resource identifier of the bottom Drawable.
185929f27aab7ac7231f3734c988d5ee7201627d535Alan Viverette     * @attr name android:drawableStart
186929f27aab7ac7231f3734c988d5ee7201627d535Alan Viverette     * @attr name android:drawableTop
187929f27aab7ac7231f3734c988d5ee7201627d535Alan Viverette     * @attr name android:drawableEnd
188929f27aab7ac7231f3734c988d5ee7201627d535Alan Viverette     * @attr name android:drawableBottom
18931a49efe2adb59e31611f6871895a3243d835127Yuichi Araki     */
19031a49efe2adb59e31611f6871895a3243d835127Yuichi Araki    public static void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
19139e84476b45f10ed4929e307372d6f7a2103e9d5Kirill Grouchnikov            @DrawableRes int start, @DrawableRes int top, @DrawableRes int end,
19239e84476b45f10ed4929e307372d6f7a2103e9d5Kirill Grouchnikov            @DrawableRes int bottom) {
1935a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        if (Build.VERSION.SDK_INT >= 18) {
1945a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            textView.setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom);
1955a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        } else if (Build.VERSION.SDK_INT >= 17) {
1965a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            boolean rtl = textView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
1975a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            textView.setCompoundDrawablesWithIntrinsicBounds(rtl ? end : start, top,
1985a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton                    rtl ? start : end, bottom);
1995a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        } else {
2005a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            textView.setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom);
2015a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        }
20231a49efe2adb59e31611f6871895a3243d835127Yuichi Araki    }
20331a49efe2adb59e31611f6871895a3243d835127Yuichi Araki
204092bd179f5a24c29a63717ce69c6d4065e33abe6Chris Banes    /**
205092bd179f5a24c29a63717ce69c6d4065e33abe6Chris Banes     * Returns the maximum number of lines displayed in the given TextView, or -1 if the maximum
206092bd179f5a24c29a63717ce69c6d4065e33abe6Chris Banes     * height was set in pixels instead.
207092bd179f5a24c29a63717ce69c6d4065e33abe6Chris Banes     */
208092bd179f5a24c29a63717ce69c6d4065e33abe6Chris Banes    public static int getMaxLines(@NonNull TextView textView) {
2095a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        if (Build.VERSION.SDK_INT >= 16) {
2105a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            return textView.getMaxLines();
2115a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        }
2125a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton
2135a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        if (!sMaxModeFieldFetched) {
2145a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            sMaxModeField = retrieveField("mMaxMode");
2155a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            sMaxModeFieldFetched = true;
2165a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        }
2175a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        if (sMaxModeField != null && retrieveIntFromField(sMaxModeField, textView) == LINES) {
2185a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            // If the max mode is using lines, we can grab the maximum value
2195a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            if (!sMaximumFieldFetched) {
2205a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton                sMaximumField = retrieveField("mMaximum");
2215a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton                sMaximumFieldFetched = true;
2225a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            }
2235a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            if (sMaximumField != null) {
2245a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton                return retrieveIntFromField(sMaximumField, textView);
2255a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            }
2265a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        }
2275a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        return -1;
228092bd179f5a24c29a63717ce69c6d4065e33abe6Chris Banes    }
229092bd179f5a24c29a63717ce69c6d4065e33abe6Chris Banes
230092bd179f5a24c29a63717ce69c6d4065e33abe6Chris Banes    /**
231092bd179f5a24c29a63717ce69c6d4065e33abe6Chris Banes     * Returns the minimum number of lines displayed in the given TextView, or -1 if the minimum
232092bd179f5a24c29a63717ce69c6d4065e33abe6Chris Banes     * height was set in pixels instead.
233092bd179f5a24c29a63717ce69c6d4065e33abe6Chris Banes     */
234092bd179f5a24c29a63717ce69c6d4065e33abe6Chris Banes    public static int getMinLines(@NonNull TextView textView) {
2355a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        if (Build.VERSION.SDK_INT >= 16) {
2365a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            return textView.getMinLines();
2375a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        }
2385a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton
2395a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        if (!sMinModeFieldFetched) {
2405a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            sMinModeField = retrieveField("mMinMode");
2415a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            sMinModeFieldFetched = true;
2425a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        }
2435a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        if (sMinModeField != null && retrieveIntFromField(sMinModeField, textView) == LINES) {
2445a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            // If the min mode is using lines, we can grab the maximum value
2455a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            if (!sMinimumFieldFetched) {
2465a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton                sMinimumField = retrieveField("mMinimum");
2475a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton                sMinimumFieldFetched = true;
2485a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            }
2495a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            if (sMinimumField != null) {
2505a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton                return retrieveIntFromField(sMinimumField, textView);
2515a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            }
2525a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        }
2535a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        return -1;
254092bd179f5a24c29a63717ce69c6d4065e33abe6Chris Banes    }
2550bd3435d26a1c5daa0d205324c327dee4992bfbeKirill Grouchnikov
2560bd3435d26a1c5daa0d205324c327dee4992bfbeKirill Grouchnikov    /**
2570bd3435d26a1c5daa0d205324c327dee4992bfbeKirill Grouchnikov     * Sets the text appearance from the specified style resource.
2580bd3435d26a1c5daa0d205324c327dee4992bfbeKirill Grouchnikov     * <p>
2590bd3435d26a1c5daa0d205324c327dee4992bfbeKirill Grouchnikov     * Use a framework-defined {@code TextAppearance} style like
2609562a3b639225d406d736b64a12e2d75459259e3Alan Viverette     * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1}.
2610bd3435d26a1c5daa0d205324c327dee4992bfbeKirill Grouchnikov     *
2620bd3435d26a1c5daa0d205324c327dee4992bfbeKirill Grouchnikov     * @param textView The TextView against which to invoke the method.
2630bd3435d26a1c5daa0d205324c327dee4992bfbeKirill Grouchnikov     * @param resId    The resource identifier of the style to apply.
2640bd3435d26a1c5daa0d205324c327dee4992bfbeKirill Grouchnikov     */
26539e84476b45f10ed4929e307372d6f7a2103e9d5Kirill Grouchnikov    public static void setTextAppearance(@NonNull TextView textView, @StyleRes int resId) {
2665a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        if (Build.VERSION.SDK_INT >= 23) {
2675a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            textView.setTextAppearance(resId);
2685a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        } else {
2695a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            textView.setTextAppearance(textView.getContext(), resId);
2705a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        }
2710bd3435d26a1c5daa0d205324c327dee4992bfbeKirill Grouchnikov    }
272ddf46923b9487ac3a021f01b55d96136d811e8beChris Banes
273ddf46923b9487ac3a021f01b55d96136d811e8beChris Banes    /**
274ddf46923b9487ac3a021f01b55d96136d811e8beChris Banes     * Returns drawables for the start, top, end, and bottom borders from the given text view.
275ddf46923b9487ac3a021f01b55d96136d811e8beChris Banes     */
276e208f97c411c9e0a09d7277d60889bb4a2d5815cJake Wharton    @NonNull
277ddf46923b9487ac3a021f01b55d96136d811e8beChris Banes    public static Drawable[] getCompoundDrawablesRelative(@NonNull TextView textView) {
2785a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        if (Build.VERSION.SDK_INT >= 18) {
2795a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            return textView.getCompoundDrawablesRelative();
2805a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        }
2815a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        if (Build.VERSION.SDK_INT >= 17) {
2825a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            final boolean rtl = textView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
2835a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            final Drawable[] compounds = textView.getCompoundDrawables();
2845a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            if (rtl) {
2855a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton                // If we're on RTL, we need to invert the horizontal result like above
2865a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton                final Drawable start = compounds[2];
2875a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton                final Drawable end = compounds[0];
2885a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton                compounds[0] = start;
2895a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton                compounds[2] = end;
2905a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            }
2915a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton            return compounds;
2925a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        }
2935a2af356c9031a9c973bc77ea68703ee1bfcec35Jake Wharton        return textView.getCompoundDrawables();
294ddf46923b9487ac3a021f01b55d96136d811e8beChris Banes    }
295cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu
296cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu    /**
297cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * Specify whether this widget should automatically scale the text to try to perfectly fit
298cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * within the layout bounds by using the default auto-size configuration.
299cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     *
300cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * @param autoSizeTextType the type of auto-size. Must be one of
301cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     *        {@link TextViewCompat#AUTO_SIZE_TEXT_TYPE_NONE} or
302cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     *        {@link TextViewCompat#AUTO_SIZE_TEXT_TYPE_UNIFORM}
303cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     *
304cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * @attr name android:autoSizeTextType
305cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     */
306a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton    @SuppressWarnings("RedundantCast") // Intentionally invoking interface method.
307e208f97c411c9e0a09d7277d60889bb4a2d5815cJake Wharton    public static void setAutoSizeTextTypeWithDefaults(@NonNull TextView textView,
308e208f97c411c9e0a09d7277d60889bb4a2d5815cJake Wharton            int autoSizeTextType) {
309a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        if (Build.VERSION.SDK_INT >= 27) {
310a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton            textView.setAutoSizeTextTypeWithDefaults(autoSizeTextType);
311a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        } else if (textView instanceof AutoSizeableTextView) {
312a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton            ((AutoSizeableTextView) textView).setAutoSizeTextTypeWithDefaults(autoSizeTextType);
313a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        }
314cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu    }
315cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu
316cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu    /**
317cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * Specify whether this widget should automatically scale the text to try to perfectly fit
318cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * within the layout bounds. If all the configuration params are valid the type of auto-size is
319cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * set to {@link TextViewCompat#AUTO_SIZE_TEXT_TYPE_UNIFORM}.
320cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     *
321cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * @param autoSizeMinTextSize the minimum text size available for auto-size
322cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * @param autoSizeMaxTextSize the maximum text size available for auto-size
323cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * @param autoSizeStepGranularity the auto-size step granularity. It is used in conjunction with
324cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     *                                the minimum and maximum text size in order to build the set of
325cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     *                                text sizes the system uses to choose from when auto-sizing
326cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * @param unit the desired dimension unit for all sizes above. See {@link TypedValue} for the
327cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     *             possible dimension units
328cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     *
329cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * @throws IllegalArgumentException if any of the configuration params are invalid.
330cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     *
331cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * @attr name android:autoSizeTextType
332cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * @attr name android:autoSizeTextType
333cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * @attr name android:autoSizeMinTextSize
334cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * @attr name android:autoSizeMaxTextSize
335cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * @attr name android:autoSizeStepGranularity
336cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     */
337a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton    @SuppressWarnings("RedundantCast") // Intentionally invoking interface method.
338cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu    public static void setAutoSizeTextTypeUniformWithConfiguration(
339e208f97c411c9e0a09d7277d60889bb4a2d5815cJake Wharton            @NonNull TextView textView,
340cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu            int autoSizeMinTextSize,
341cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu            int autoSizeMaxTextSize,
342cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu            int autoSizeStepGranularity,
343cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu            int unit) throws IllegalArgumentException {
344a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        if (Build.VERSION.SDK_INT >= 27) {
345a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton            textView.setAutoSizeTextTypeUniformWithConfiguration(
346a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton                    autoSizeMinTextSize, autoSizeMaxTextSize, autoSizeStepGranularity, unit);
347a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        } else if (textView instanceof AutoSizeableTextView) {
348a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton            ((AutoSizeableTextView) textView).setAutoSizeTextTypeUniformWithConfiguration(
349a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton                    autoSizeMinTextSize, autoSizeMaxTextSize, autoSizeStepGranularity, unit);
350a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        }
351cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu    }
352cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu
353cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu    /**
354cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * Specify whether this widget should automatically scale the text to try to perfectly fit
355cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * within the layout bounds. If at least one value from the <code>presetSizes</code> is valid
356cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * then the type of auto-size is set to {@link TextViewCompat#AUTO_SIZE_TEXT_TYPE_UNIFORM}.
357cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     *
358cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * @param presetSizes an {@code int} array of sizes in pixels
359cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * @param unit the desired dimension unit for the preset sizes above. See {@link TypedValue} for
360cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     *             the possible dimension units
361cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     *
362cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * @throws IllegalArgumentException if all of the <code>presetSizes</code> are invalid.
363cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     *_
364cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * @attr name android:autoSizeTextType
365cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * @attr name android:autoSizePresetSizes
366cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     */
367a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton    @SuppressWarnings("RedundantCast") // Intentionally invoking interface method.
368e208f97c411c9e0a09d7277d60889bb4a2d5815cJake Wharton    public static void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull TextView textView,
369cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu            @NonNull int[] presetSizes, int unit) throws IllegalArgumentException {
370a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        if (Build.VERSION.SDK_INT >= 27) {
371a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton            textView.setAutoSizeTextTypeUniformWithPresetSizes(presetSizes, unit);
372a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        } else if (textView instanceof AutoSizeableTextView) {
373a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton            ((AutoSizeableTextView) textView).setAutoSizeTextTypeUniformWithPresetSizes(
374a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton                    presetSizes, unit);
375a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        }
376cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu    }
377cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu
378cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu    /**
379cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * Returns the type of auto-size set for this widget.
380cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     *
381cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * @return an {@code int} corresponding to one of the auto-size types:
382cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     *         {@link TextViewCompat#AUTO_SIZE_TEXT_TYPE_NONE} or
383cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     *         {@link TextViewCompat#AUTO_SIZE_TEXT_TYPE_UNIFORM}
384cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     *
385cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * @attr name android:autoSizeTextType
386cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     */
387a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton    @SuppressWarnings("RedundantCast") // Intentionally invoking interface method.
388e208f97c411c9e0a09d7277d60889bb4a2d5815cJake Wharton    public static int getAutoSizeTextType(@NonNull TextView textView) {
389a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        if (Build.VERSION.SDK_INT >= 27) {
390a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton            return textView.getAutoSizeTextType();
391a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        }
392a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        if (textView instanceof AutoSizeableTextView) {
393a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton            return ((AutoSizeableTextView) textView).getAutoSizeTextType();
394a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        }
395a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        return AUTO_SIZE_TEXT_TYPE_NONE;
396cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu    }
397cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu
398cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu    /**
399cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * @return the current auto-size step granularity in pixels.
400cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     *
401cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * @attr name android:autoSizeStepGranularity
402cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     */
403a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton    @SuppressWarnings("RedundantCast") // Intentionally invoking interface method.
404e208f97c411c9e0a09d7277d60889bb4a2d5815cJake Wharton    public static int getAutoSizeStepGranularity(@NonNull TextView textView) {
405a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        if (Build.VERSION.SDK_INT >= 27) {
406a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton            return textView.getAutoSizeStepGranularity();
407a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        }
408a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        if (textView instanceof AutoSizeableTextView) {
409a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton            return ((AutoSizeableTextView) textView).getAutoSizeStepGranularity();
410a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        }
411a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        return -1;
412cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu    }
413cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu
414cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu    /**
415cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * @return the current auto-size minimum text size in pixels (the default is 12sp). Note that
416cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     *         if auto-size has not been configured this function returns {@code -1}.
417cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     *
418cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * @attr name android:autoSizeMinTextSize
419cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     */
420a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton    @SuppressWarnings("RedundantCast") // Intentionally invoking interface method.
421e208f97c411c9e0a09d7277d60889bb4a2d5815cJake Wharton    public static int getAutoSizeMinTextSize(@NonNull TextView textView) {
422a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        if (Build.VERSION.SDK_INT >= 27) {
423a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton            return textView.getAutoSizeMinTextSize();
424a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        }
425a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        if (textView instanceof AutoSizeableTextView) {
426a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton            return ((AutoSizeableTextView) textView).getAutoSizeMinTextSize();
427a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        }
428a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        return -1;
429cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu    }
430cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu
431cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu    /**
432cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * @return the current auto-size maximum text size in pixels (the default is 112sp). Note that
433cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     *         if auto-size has not been configured this function returns {@code -1}.
434cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     *
435cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * @attr name android:autoSizeMaxTextSize
436cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     */
437a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton    @SuppressWarnings("RedundantCast") // Intentionally invoking interface method.
438e208f97c411c9e0a09d7277d60889bb4a2d5815cJake Wharton    public static int getAutoSizeMaxTextSize(@NonNull TextView textView) {
439a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        if (Build.VERSION.SDK_INT >= 27) {
440a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton            return textView.getAutoSizeMaxTextSize();
441a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        }
442a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        if (textView instanceof AutoSizeableTextView) {
443a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton            return ((AutoSizeableTextView) textView).getAutoSizeMaxTextSize();
444a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        }
445a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        return -1;
446cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu    }
447cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu
448cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu    /**
449cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * @return the current auto-size {@code int} sizes array (in pixels).
450cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     *
451cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     * @attr name android:autoSizePresetSizes
452cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu     */
453e208f97c411c9e0a09d7277d60889bb4a2d5815cJake Wharton    @NonNull
454a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton    @SuppressWarnings("RedundantCast") // Intentionally invoking interface method.
455e208f97c411c9e0a09d7277d60889bb4a2d5815cJake Wharton    public static int[] getAutoSizeTextAvailableSizes(@NonNull TextView textView) {
456a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        if (Build.VERSION.SDK_INT >= 27) {
457a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton            return textView.getAutoSizeTextAvailableSizes();
458a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        }
459a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        if (textView instanceof AutoSizeableTextView) {
460a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton            return ((AutoSizeableTextView) textView).getAutoSizeTextAvailableSizes();
461a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        }
462a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        return new int[0];
463cc93b432d3b66d1127b1d71cf1b95eb8a21ef319Andrei Stingaceanu    }
464957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popa
465957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popa    /**
466957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popa     * Sets a selection action mode callback on a TextView.
467957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popa     *
468957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popa     * Also this method can be used to fix a bug in framework SDK 26. On these affected devices,
469957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popa     * the bug causes the menu containing the options for handling ACTION_PROCESS_TEXT after text
470957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popa     * selection to miss a number of items. This method can be used to fix this wrong behaviour for
471957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popa     * a text view, by passing any custom callback implementation. If no custom callback is desired,
472957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popa     * a no-op implementation should be provided.
473957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popa     *
474957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popa     * Note that, by default, the bug will only be fixed when the default floating toolbar menu
475957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popa     * implementation is used. If a custom implementation of {@link Menu} is provided, this should
476957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popa     * provide the method Menu#removeItemAt(int) which removes a menu item by its position,
477957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popa     * as given by Menu#getItem(int). Also, the following post condition should hold: a call
478957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popa     * to removeItemAt(i), should not modify the results of getItem(j) for any j < i. Intuitively,
479957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popa     * removing an element from the menu should behave as removing an element from a list.
480957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popa     * Note that this method does not exist in the {@link Menu} interface. However, it is required,
481957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popa     * and going to be called by reflection, in order to display the correct process text items in
482957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popa     * the menu.
483957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popa     *
484957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popa     * @param textView The TextView to set the action selection mode callback on.
485957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popa     * @param callback The action selection mode callback to set on textView.
486957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popa     */
487a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton    public static void setCustomSelectionActionModeCallback(@NonNull final TextView textView,
488a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton                @NonNull final ActionMode.Callback callback) {
489a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        if (Build.VERSION.SDK_INT < 26 || Build.VERSION.SDK_INT > 27) {
490a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton            textView.setCustomSelectionActionModeCallback(callback);
491a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton            return;
492a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        }
493a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton
494a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        // A bug in O and O_MR1 causes a number of options for handling the ACTION_PROCESS_TEXT
495a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        // intent after selection to not be displayed in the menu, although they should be.
496a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        // Here we fix this, by removing the menu items created by the framework code, and
497a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton        // adding them (and the missing ones) back correctly.
49869decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        textView.setCustomSelectionActionModeCallback(new OreoCallback(callback, textView));
49969decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton    }
500a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton
50169decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton    @TargetApi(26) // TODO was anonymous but https://issuetracker.google.com/issues/76458979
50269decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton    private static class OreoCallback implements ActionMode.Callback {
50369decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        // This constant should be correlated with its definition in the
50469decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        // android.widget.Editor class.
50569decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        private static final int MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START = 100;
50669decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        private final ActionMode.Callback mCallback;
50769decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        private final TextView mTextView;
50869decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton
50969decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        // References to the MenuBuilder class and its removeItemAt(int) method.
51069decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        // Since in most cases the menu instance processed by this callback is going
51169decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        // to be a MenuBuilder, we keep these references to avoid querying for them
51269decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        // frequently by reflection in recomputeProcessTextMenuItems.
51369decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        private Class mMenuBuilderClass;
51469decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        private Method mMenuBuilderRemoveItemAtMethod;
51569decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        private boolean mCanUseMenuBuilderReferences;
51669decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        private boolean mInitializedMenuBuilderReferences;
51769decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton
51869decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        OreoCallback(ActionMode.Callback callback, TextView textView) {
51969decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton            mCallback = callback;
52069decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton            mTextView = textView;
52169decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton            mInitializedMenuBuilderReferences = false;
52269decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        }
523a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton
52469decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        @Override
52569decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
52669decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton            return mCallback.onCreateActionMode(mode, menu);
52769decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        }
528a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton
52969decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        @Override
53069decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
53169decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton            recomputeProcessTextMenuItems(menu);
53269decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton            return mCallback.onPrepareActionMode(mode, menu);
53369decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        }
534a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton
53569decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        @Override
53669decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
53769decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton            return mCallback.onActionItemClicked(mode, item);
53869decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        }
53969decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton
54069decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        @Override
54169decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        public void onDestroyActionMode(ActionMode mode) {
54269decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton            mCallback.onDestroyActionMode(mode);
54369decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        }
54469decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton
54569decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        private void recomputeProcessTextMenuItems(final Menu menu) {
54669decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton            final Context context = mTextView.getContext();
54769decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton            final PackageManager packageManager = context.getPackageManager();
54869decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton
54969decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton            if (!mInitializedMenuBuilderReferences) {
55069decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                mInitializedMenuBuilderReferences = true;
551a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton                try {
55269decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                    mMenuBuilderClass =
55369decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                            Class.forName("com.android.internal.view.menu.MenuBuilder");
55469decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                    mMenuBuilderRemoveItemAtMethod = mMenuBuilderClass
55569decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                            .getDeclaredMethod("removeItemAt", Integer.TYPE);
55669decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                    mCanUseMenuBuilderReferences = true;
55769decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                } catch (ClassNotFoundException | NoSuchMethodException e) {
55869decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                    mMenuBuilderClass = null;
55969decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                    mMenuBuilderRemoveItemAtMethod = null;
56069decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                    mCanUseMenuBuilderReferences = false;
56169decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                }
56269decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton            }
56369decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton            // Remove the menu items created for ACTION_PROCESS_TEXT handlers.
56469decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton            try {
56569decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                final Method removeItemAtMethod =
56669decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                        (mCanUseMenuBuilderReferences && mMenuBuilderClass.isInstance(menu))
56769decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                                ? mMenuBuilderRemoveItemAtMethod
56869decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                                : menu.getClass()
56969decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                                        .getDeclaredMethod("removeItemAt", Integer.TYPE);
57069decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                for (int i = menu.size() - 1; i >= 0; --i) {
57169decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                    final MenuItem item = menu.getItem(i);
57269decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                    if (item.getIntent() != null && Intent.ACTION_PROCESS_TEXT
57369decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                            .equals(item.getIntent().getAction())) {
57469decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                        removeItemAtMethod.invoke(menu, i);
575a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton                    }
576a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton                }
57769decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton            } catch (NoSuchMethodException | IllegalAccessException
57869decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                    | InvocationTargetException e) {
57969decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                // There is a menu custom implementation used which is not providing
58069decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                // a removeItemAt(int) menu. There is nothing we can do in this case.
58169decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                return;
58269decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton            }
583a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton
58469decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton            // Populate the menu again with the ACTION_PROCESS_TEXT handlers.
58569decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton            final List<ResolveInfo> supportedActivities =
58669decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                    getSupportedActivities(context, packageManager);
58769decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton            for (int i = 0; i < supportedActivities.size(); ++i) {
58869decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                final ResolveInfo info = supportedActivities.get(i);
58969decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                menu.add(Menu.NONE, Menu.NONE,
59069decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                        MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START + i,
59169decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                        info.loadLabel(packageManager))
59269decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                        .setIntent(createProcessTextIntentForResolveInfo(info, mTextView))
59369decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                        .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
594a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton            }
59569decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        }
596a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton
59769decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        private List<ResolveInfo> getSupportedActivities(final Context context,
59869decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                final PackageManager packageManager) {
59969decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton            final List<ResolveInfo> supportedActivities = new ArrayList<>();
60069decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton            boolean canStartActivityForResult = context instanceof Activity;
60169decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton            if (!canStartActivityForResult) {
602a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton                return supportedActivities;
603a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton            }
60469decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton            final List<ResolveInfo> unfiltered =
60569decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                    packageManager.queryIntentActivities(createProcessTextIntent(), 0);
60669decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton            for (ResolveInfo info : unfiltered) {
60769decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                if (isSupportedActivity(info, context)) {
60869decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                    supportedActivities.add(info);
609a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton                }
610a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton            }
61169decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton            return supportedActivities;
61269decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        }
613a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton
61469decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        private boolean isSupportedActivity(final ResolveInfo info, final Context context) {
61569decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton            if (context.getPackageName().equals(info.activityInfo.packageName)) {
61669decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                return true;
617a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton            }
61869decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton            if (!info.activityInfo.exported) {
61969decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                return false;
620a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton            }
62169decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton            return info.activityInfo.permission == null
62269decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                    || context.checkSelfPermission(info.activityInfo.permission)
62369decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                        == PackageManager.PERMISSION_GRANTED;
62469decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        }
625a70d31d50799d535634acc9df9296d5ccd173198Jake Wharton
62669decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        private Intent createProcessTextIntentForResolveInfo(final ResolveInfo info,
62769decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                final TextView textView11) {
62869decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton            return createProcessTextIntent()
62969decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                    .putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, !isEditable(textView11))
63069decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                    .setClassName(info.activityInfo.packageName, info.activityInfo.name);
63169decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        }
63269decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton
63369decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        private boolean isEditable(final TextView textView11) {
63469decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton            return textView11 instanceof Editable
63569decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                    && textView11.onCheckIsTextEditor()
63669decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton                    && textView11.isEnabled();
63769decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        }
63869decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton
63969decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        private Intent createProcessTextIntent() {
64069decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton            return new Intent().setAction(Intent.ACTION_PROCESS_TEXT).setType("text/plain");
64169decef5d945e46fcebfc3ee33ced2070987bf70Jake Wharton        }
642957dff2d0b5909076b9cde2ee6e770715c357b83Mihai Popa    }
643329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa
644329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa    /**
645329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     * Updates the top padding of the TextView so that {@code firstBaselineToTopHeight} is
646329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     * equal to the distance between the first text baseline and the top of this TextView.
647329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     * <strong>Note</strong> that if {@code FontMetrics.top} or {@code FontMetrics.ascent} was
648329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     * already greater than {@code firstBaselineToTopHeight}, the top padding is not updated.
649329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     *
650329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     * @param firstBaselineToTopHeight distance between first baseline to top of the container
651329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     *      in pixels
652329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     *
653329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     * @see #getFirstBaselineToTopHeight(TextView)
654329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     * @see TextView#setPadding(int, int, int, int)
655329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     * @see TextView#setPaddingRelative(int, int, int, int)
656329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     *
657329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     * @attr name android:firstBaselineToTopHeight
658329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     */
659329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa    public static void setFirstBaselineToTopHeight(
660329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa            @NonNull final TextView textView,
661329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa            @Px @IntRange(from = 0) final int firstBaselineToTopHeight) {
662329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa        Preconditions.checkArgumentNonnegative(firstBaselineToTopHeight);
663329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa        if (BuildCompat.isAtLeastP()) {
664329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa            textView.setFirstBaselineToTopHeight(firstBaselineToTopHeight);
665329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa            return;
666329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa        }
667329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa
668329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa        final Paint.FontMetricsInt fontMetrics = textView.getPaint().getFontMetricsInt();
669329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa        final int fontMetricsTop;
670329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN
671329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa                // The includeFontPadding attribute was introduced
672329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa                // in SDK16, and it is true by default.
673329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa                || textView.getIncludeFontPadding()) {
674329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa            fontMetricsTop = fontMetrics.top;
675329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa        } else {
676329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa            fontMetricsTop = fontMetrics.ascent;
677329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa        }
678329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa
679329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa        // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size
680329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa        // in settings). At the moment, we don't.
681329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa
682329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa        if (firstBaselineToTopHeight > Math.abs(fontMetricsTop)) {
683329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa            final int paddingTop = firstBaselineToTopHeight - (-fontMetricsTop);
684329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa            textView.setPadding(textView.getPaddingLeft(), paddingTop,
685329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa                    textView.getPaddingRight(), textView.getPaddingBottom());
686329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa        }
687329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa    }
688329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa
689329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa    /**
690329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     * Updates the bottom padding of the TextView so that {@code lastBaselineToBottomHeight} is
691329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     * equal to the distance between the last text baseline and the bottom of this TextView.
692329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     * <strong>Note</strong> that if {@code FontMetrics.bottom} or {@code FontMetrics.descent} was
693329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     * already greater than {@code lastBaselineToBottomHeight}, the bottom padding is not updated.
694329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     *
695329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     * @param lastBaselineToBottomHeight distance between last baseline to bottom of the container
696329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     *      in pixels
697329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     *
698329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     * @see #getLastBaselineToBottomHeight(TextView)
699329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     * @see TextView#setPadding(int, int, int, int)
700329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     * @see TextView#setPaddingRelative(int, int, int, int)
701329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     *
702329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     * @attr name android:lastBaselineToBottomHeight
703329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     */
704329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa    public static void setLastBaselineToBottomHeight(
705329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa            @NonNull final TextView textView,
706329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa            @Px @IntRange(from = 0) int lastBaselineToBottomHeight) {
707329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa        Preconditions.checkArgumentNonnegative(lastBaselineToBottomHeight);
708329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa
709329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa        final Paint.FontMetricsInt fontMetrics = textView.getPaint().getFontMetricsInt();
710329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa        final int fontMetricsBottom;
711329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN
712329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa                // The includeFontPadding attribute was introduced
713329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa                // in SDK16, and it is true by default.
714329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa                || textView.getIncludeFontPadding()) {
715329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa            fontMetricsBottom = fontMetrics.bottom;
716329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa        } else {
717329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa            fontMetricsBottom = fontMetrics.descent;
718329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa        }
719329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa
720329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa        // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size
721329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa        // in settings). At the moment, we don't.
722329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa
723329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa        if (lastBaselineToBottomHeight > Math.abs(fontMetricsBottom)) {
724329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa            final int paddingBottom = lastBaselineToBottomHeight - fontMetricsBottom;
725329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa            textView.setPadding(textView.getPaddingLeft(), textView.getPaddingTop(),
726329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa                    textView.getPaddingRight(), paddingBottom);
727329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa        }
728329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa    }
729329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa
730329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa    /**
731329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     * Returns the distance between the first text baseline and the top of this TextView.
732329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     *
733329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     * @see #setFirstBaselineToTopHeight(TextView, int)
734329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     * @attr name android:firstBaselineToTopHeight
735329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     */
736329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa    public static int getFirstBaselineToTopHeight(@NonNull final TextView textView) {
737329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa        return textView.getPaddingTop() - textView.getPaint().getFontMetricsInt().top;
738329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa    }
739329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa
740329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa    /**
741329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     * Returns the distance between the last text baseline and the bottom of this TextView.
742329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     *
743329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     * @see #setLastBaselineToBottomHeight(TextView, int)
744329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     * @attr name android:lastBaselineToBottomHeight
745329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     */
746329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa    public static int getLastBaselineToBottomHeight(@NonNull final TextView textView) {
747329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa        return textView.getPaddingBottom() + textView.getPaint().getFontMetricsInt().bottom;
748329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa    }
749329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa
750329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa
751329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa    /**
752329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     * Sets an explicit line height for this TextView. This is equivalent to the vertical distance
753329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     * between subsequent baselines in the TextView.
754329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     *
755329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     * @param lineHeight the line height in pixels
756329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     *
757329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     * @see TextView#setLineSpacing(float, float)
758329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     * @see TextView#getLineSpacingExtra()
759329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     * @see TextView#getLineSpacingMultiplier()
760329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     *
761329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     * @attr name android:lineHeight
762329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa     */
763329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa    public static void setLineHeight(@NonNull final TextView textView,
764329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa                              @Px @IntRange(from = 0) int lineHeight) {
765329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa        Preconditions.checkArgumentNonnegative(lineHeight);
766329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa
767329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa        final int fontHeight = textView.getPaint().getFontMetricsInt(null);
768329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa        // Make sure we don't setLineSpacing if it's not needed to avoid unnecessary redraw.
769329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa        if (lineHeight != fontHeight) {
770329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa            // Set lineSpacingExtra by the difference of lineSpacing with lineHeight
771329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa            textView.setLineSpacing(lineHeight - fontHeight, 1f);
772329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa        }
773329cc6d9130712aae9ef20001eddd0ba5114c9deMihai Popa    }
77431a49efe2adb59e31611f6871895a3243d835127Yuichi Araki}
775