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