1fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes/* 2ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikas * Copyright 2018 The Android Open Source Project 3fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes * 4fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes * Licensed under the Apache License, Version 2.0 (the "License"); 5fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes * you may not use this file except in compliance with the License. 6fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes * You may obtain a copy of the License at 7fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes * 8fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes * http://www.apache.org/licenses/LICENSE-2.0 9fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes * 10fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes * Unless required by applicable law or agreed to in writing, software 11fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes * distributed under the License is distributed on an "AS IS" BASIS, 12fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes * See the License for the specific language governing permissions and 14fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes * limitations under the License. 15fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes */ 16fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes 17ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikaspackage androidx.core.view; 18fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes 194bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikasimport android.content.Context; 20fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banesimport android.os.Build; 214bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikasimport android.util.AttributeSet; 224bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikasimport android.util.Log; 23fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banesimport android.view.LayoutInflater; 244bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikasimport android.view.View; 254bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas 269dede51868bbbe16aadcd65e04860bea8ea50e05Aurimas Liutikasimport androidx.annotation.NonNull; 279dede51868bbbe16aadcd65e04860bea8ea50e05Aurimas Liutikas 284bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikasimport java.lang.reflect.Field; 29fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes 30fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes/** 31645e5c8aa6b31961c5f73f3d30bb5261d5e04aebKirill Grouchnikov * Helper for accessing features in {@link LayoutInflater}. 32fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes */ 33c5847d13e40f5d52459f5c0dab32dc08f1a9a683Chris Banespublic final class LayoutInflaterCompat { 344bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas private static final String TAG = "LayoutInflaterCompatHC"; 35fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes 364bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas private static Field sLayoutInflaterFactory2Field; 374bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas private static boolean sCheckedField; 384bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas 394bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas @SuppressWarnings("deprecation") 404bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas static class Factory2Wrapper implements LayoutInflater.Factory2 { 414bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas final LayoutInflaterFactory mDelegateFactory; 424bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas 434bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas Factory2Wrapper(LayoutInflaterFactory delegateFactory) { 444bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas mDelegateFactory = delegateFactory; 454bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas } 46fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes 47fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes @Override 484bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas public View onCreateView(String name, Context context, AttributeSet attrs) { 494bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas return mDelegateFactory.onCreateView(null, name, context, attrs); 50fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes } 5156594b0cf74a9f71c52980044283ddb0e9e6f47cYuichi Araki 5256594b0cf74a9f71c52980044283ddb0e9e6f47cYuichi Araki @Override 534bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas public View onCreateView(View parent, String name, Context context, 544bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas AttributeSet attributeSet) { 554bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas return mDelegateFactory.onCreateView(parent, name, context, attributeSet); 564bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas } 574bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas 58fa79d6fc148a0642f240377b8ce82acfee7bb890Aurimas Liutikas @Override 594bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas public String toString() { 604bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas return getClass().getName() + "{" + mDelegateFactory + "}"; 6156594b0cf74a9f71c52980044283ddb0e9e6f47cYuichi Araki } 62fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes } 63fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes 644bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas /** 654bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas * For APIs < 21, there was a framework bug that prevented a LayoutInflater's 664bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas * Factory2 from being merged properly if set after a cloneInContext from a LayoutInflater 674bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas * that already had a Factory2 registered. We work around that bug here. If we can't we 684bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas * log an error. 694bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas */ 7004356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton private static void forceSetFactory2(LayoutInflater inflater, LayoutInflater.Factory2 factory) { 714bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas if (!sCheckedField) { 724bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas try { 734bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas sLayoutInflaterFactory2Field = LayoutInflater.class.getDeclaredField("mFactory2"); 744bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas sLayoutInflaterFactory2Field.setAccessible(true); 754bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas } catch (NoSuchFieldException e) { 764bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas Log.e(TAG, "forceSetFactory2 Could not find field 'mFactory2' on class " 774bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas + LayoutInflater.class.getName() 784bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas + "; inflation may have unexpected results.", e); 794bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas } 804bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas sCheckedField = true; 814bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas } 824bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas if (sLayoutInflaterFactory2Field != null) { 834bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas try { 844bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas sLayoutInflaterFactory2Field.set(inflater, factory); 854bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas } catch (IllegalAccessException e) { 864bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas Log.e(TAG, "forceSetFactory2 could not set the Factory2 on LayoutInflater " 874bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas + inflater + "; inflation may have unexpected results.", e); 884bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas } 89fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes } 90fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes } 91fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes 92fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes /* 93fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes * Hide the constructor. 94fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes */ 95fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes private LayoutInflaterCompat() { 96fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes } 97fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes 98fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes /** 99fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes * Attach a custom Factory interface for creating views while using 100fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes * this LayoutInflater. This must not be null, and can only be set once; 101fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes * after setting, you can not change the factory. 102fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes * 103fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes * @see LayoutInflater#setFactory(android.view.LayoutInflater.Factory) 1044bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas * 1054bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas * @deprecated Use {@link #setFactory2(LayoutInflater, LayoutInflater.Factory2)} instead to set 1064bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas * and {@link LayoutInflater#getFactory2()} to get the factory. 107fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes */ 1084bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas @Deprecated 1094bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas public static void setFactory( 1104bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas @NonNull LayoutInflater inflater, @NonNull LayoutInflaterFactory factory) { 11104356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton if (Build.VERSION.SDK_INT >= 21) { 11204356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton inflater.setFactory2(factory != null ? new Factory2Wrapper(factory) : null); 11304356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton } else { 11404356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton final LayoutInflater.Factory2 factory2 = factory != null 11504356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton ? new Factory2Wrapper(factory) : null; 11604356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton inflater.setFactory2(factory2); 11704356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton 11804356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton final LayoutInflater.Factory f = inflater.getFactory(); 11904356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton if (f instanceof LayoutInflater.Factory2) { 12004356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton // The merged factory is now set to getFactory(), but not getFactory2() (pre-v21). 12104356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton // We will now try and force set the merged factory to mFactory2 12204356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton forceSetFactory2(inflater, (LayoutInflater.Factory2) f); 12304356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton } else { 12404356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton // Else, we will force set the original wrapped Factory2 12504356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton forceSetFactory2(inflater, factory2); 12604356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton } 12704356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton } 128fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes } 129fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes 13056594b0cf74a9f71c52980044283ddb0e9e6f47cYuichi Araki /** 1314bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas * Attach a custom {@link LayoutInflater.Factory2} for creating views while using 1324bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas * this {@link LayoutInflater}. This must not be null, and can only be set once; 1334bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas * after setting, you can not change the factory. 1344bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas * 1354bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas * @see LayoutInflater#setFactory2(android.view.LayoutInflater.Factory2) 1364bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas */ 1374bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas public static void setFactory2( 1384bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas @NonNull LayoutInflater inflater, @NonNull LayoutInflater.Factory2 factory) { 13904356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton inflater.setFactory2(factory); 14004356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton 14104356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton if (Build.VERSION.SDK_INT < 21) { 14204356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton final LayoutInflater.Factory f = inflater.getFactory(); 14304356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton if (f instanceof LayoutInflater.Factory2) { 14404356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton // The merged factory is now set to getFactory(), but not getFactory2() (pre-v21). 14504356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton // We will now try and force set the merged factory to mFactory2 14604356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton forceSetFactory2(inflater, (LayoutInflater.Factory2) f); 14704356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton } else { 14804356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton // Else, we will force set the original wrapped Factory2 14904356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton forceSetFactory2(inflater, factory); 15004356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton } 15104356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton } 1524bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas } 1534bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas 1544bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas /** 15556594b0cf74a9f71c52980044283ddb0e9e6f47cYuichi Araki * Return the current {@link LayoutInflaterFactory} (or null). This is 15656594b0cf74a9f71c52980044283ddb0e9e6f47cYuichi Araki * called on each element name. If the factory returns a View, add that 15756594b0cf74a9f71c52980044283ddb0e9e6f47cYuichi Araki * to the hierarchy. If it returns null, proceed to call onCreateView(name). 15856594b0cf74a9f71c52980044283ddb0e9e6f47cYuichi Araki * 15956594b0cf74a9f71c52980044283ddb0e9e6f47cYuichi Araki * @return The {@link LayoutInflaterFactory} associated with the 16056594b0cf74a9f71c52980044283ddb0e9e6f47cYuichi Araki * {@link LayoutInflater}. Will be {@code null} if the inflater does not 16156594b0cf74a9f71c52980044283ddb0e9e6f47cYuichi Araki * have a {@link LayoutInflaterFactory} but a raw {@link LayoutInflater.Factory}. 16256594b0cf74a9f71c52980044283ddb0e9e6f47cYuichi Araki * @see LayoutInflater#getFactory() 1634bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas * 1644bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas * @deprecated Use {@link #setFactory2(LayoutInflater, LayoutInflater.Factory2)} to set and 1654bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas * {@link LayoutInflater#getFactory2()} to get the factory. 16656594b0cf74a9f71c52980044283ddb0e9e6f47cYuichi Araki */ 1674bf8c3d1aeb944a993c946db770604b55f981341Aurimas Liutikas @Deprecated 16856594b0cf74a9f71c52980044283ddb0e9e6f47cYuichi Araki public static LayoutInflaterFactory getFactory(LayoutInflater inflater) { 16904356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton LayoutInflater.Factory factory = inflater.getFactory(); 17004356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton if (factory instanceof Factory2Wrapper) { 17104356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton return ((Factory2Wrapper) factory).mDelegateFactory; 17204356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton } 17304356993793e8870eca6ffca9b408c1b7ff74803Jake Wharton return null; 17456594b0cf74a9f71c52980044283ddb0e9e6f47cYuichi Araki } 175fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes} 176