1 /* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.view; 18 19 import android.annotation.Nullable; 20 import android.annotation.StyleRes; 21 import android.compat.annotation.UnsupportedAppUsage; 22 import android.content.Context; 23 import android.content.ContextWrapper; 24 import android.content.res.AssetManager; 25 import android.content.res.Configuration; 26 import android.content.res.Resources; 27 import android.os.Build; 28 29 /** 30 * A context wrapper that allows you to modify or replace the theme of the 31 * wrapped context. 32 */ 33 public class ContextThemeWrapper extends ContextWrapper { 34 @UnsupportedAppUsage 35 private int mThemeResource; 36 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768723) 37 private Resources.Theme mTheme; 38 @UnsupportedAppUsage 39 private LayoutInflater mInflater; 40 private Configuration mOverrideConfiguration; 41 @UnsupportedAppUsage 42 private Resources mResources; 43 44 /** 45 * Creates a new context wrapper with no theme and no base context. 46 * <p class="note"> 47 * <strong>Note:</strong> A base context <strong>must</strong> be attached 48 * using {@link #attachBaseContext(Context)} before calling any other 49 * method on the newly constructed context wrapper. 50 */ ContextThemeWrapper()51 public ContextThemeWrapper() { 52 super(null); 53 } 54 55 /** 56 * Creates a new context wrapper with the specified theme. 57 * <p> 58 * The specified theme will be applied on top of the base context's theme. 59 * Any attributes not explicitly defined in the theme identified by 60 * <var>themeResId</var> will retain their original values. 61 * 62 * @param base the base context 63 * @param themeResId the resource ID of the theme to be applied on top of 64 * the base context's theme 65 */ ContextThemeWrapper(Context base, @StyleRes int themeResId)66 public ContextThemeWrapper(Context base, @StyleRes int themeResId) { 67 super(base); 68 mThemeResource = themeResId; 69 } 70 71 /** 72 * Creates a new context wrapper with the specified theme. 73 * <p> 74 * Unlike {@link #ContextThemeWrapper(Context, int)}, the theme passed to 75 * this constructor will completely replace the base context's theme. 76 * 77 * @param base the base context 78 * @param theme the theme against which resources should be inflated 79 */ ContextThemeWrapper(Context base, Resources.Theme theme)80 public ContextThemeWrapper(Context base, Resources.Theme theme) { 81 super(base); 82 mTheme = theme; 83 } 84 85 @Override attachBaseContext(Context newBase)86 protected void attachBaseContext(Context newBase) { 87 super.attachBaseContext(newBase); 88 } 89 90 /** 91 * Call to set an "override configuration" on this context -- this is 92 * a configuration that replies one or more values of the standard 93 * configuration that is applied to the context. See 94 * {@link Context#createConfigurationContext(Configuration)} for more 95 * information. 96 * 97 * <p>This method can only be called once, and must be called before any 98 * calls to {@link #getResources()} or {@link #getAssets()} are made. 99 */ applyOverrideConfiguration(Configuration overrideConfiguration)100 public void applyOverrideConfiguration(Configuration overrideConfiguration) { 101 if (mResources != null) { 102 throw new IllegalStateException( 103 "getResources() or getAssets() has already been called"); 104 } 105 if (mOverrideConfiguration != null) { 106 throw new IllegalStateException("Override configuration has already been set"); 107 } 108 mOverrideConfiguration = new Configuration(overrideConfiguration); 109 } 110 111 /** 112 * Used by ActivityThread to apply the overridden configuration to onConfigurationChange 113 * callbacks. 114 * @hide 115 */ getOverrideConfiguration()116 public Configuration getOverrideConfiguration() { 117 return mOverrideConfiguration; 118 } 119 120 @Override getAssets()121 public AssetManager getAssets() { 122 // Ensure we're returning assets with the correct configuration. 123 return getResourcesInternal().getAssets(); 124 } 125 126 @Override getResources()127 public Resources getResources() { 128 return getResourcesInternal(); 129 } 130 getResourcesInternal()131 private Resources getResourcesInternal() { 132 if (mResources == null) { 133 if (mOverrideConfiguration == null) { 134 mResources = super.getResources(); 135 } else { 136 final Context resContext = createConfigurationContext(mOverrideConfiguration); 137 mResources = resContext.getResources(); 138 } 139 } 140 return mResources; 141 } 142 143 @Override setTheme(int resid)144 public void setTheme(int resid) { 145 if (mThemeResource != resid) { 146 mThemeResource = resid; 147 initializeTheme(); 148 } 149 } 150 151 /** 152 * Set the configure the current theme. If null is provided then the default Theme is returned 153 * on the next call to {@link #getTheme()} 154 * @param theme Theme to consume in the wrapper, a value of null resets the theme to the default 155 */ setTheme(@ullable Resources.Theme theme)156 public void setTheme(@Nullable Resources.Theme theme) { 157 mTheme = theme; 158 } 159 160 /** @hide */ 161 @Override 162 @UnsupportedAppUsage getThemeResId()163 public int getThemeResId() { 164 return mThemeResource; 165 } 166 167 @Override getTheme()168 public Resources.Theme getTheme() { 169 if (mTheme != null) { 170 return mTheme; 171 } 172 173 mThemeResource = Resources.selectDefaultTheme(mThemeResource, 174 getApplicationInfo().targetSdkVersion); 175 initializeTheme(); 176 177 return mTheme; 178 } 179 180 @Override getSystemService(String name)181 public Object getSystemService(String name) { 182 if (LAYOUT_INFLATER_SERVICE.equals(name)) { 183 if (mInflater == null) { 184 mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this); 185 } 186 return mInflater; 187 } 188 return getBaseContext().getSystemService(name); 189 } 190 191 /** 192 * Called by {@link #setTheme} and {@link #getTheme} to apply a theme 193 * resource to the current Theme object. May be overridden to change the 194 * default (simple) behavior. This method will not be called in multiple 195 * threads simultaneously. 196 * 197 * @param theme the theme being modified 198 * @param resId the style resource being applied to <var>theme</var> 199 * @param first {@code true} if this is the first time a style is being 200 * applied to <var>theme</var> 201 */ onApplyThemeResource(Resources.Theme theme, int resId, boolean first)202 protected void onApplyThemeResource(Resources.Theme theme, int resId, boolean first) { 203 theme.applyStyle(resId, true); 204 } 205 206 @UnsupportedAppUsage initializeTheme()207 private void initializeTheme() { 208 final boolean first = mTheme == null; 209 if (first) { 210 mTheme = getResources().newTheme(); 211 final Resources.Theme theme = getBaseContext().getTheme(); 212 if (theme != null) { 213 mTheme.setTo(theme); 214 } 215 } 216 onApplyThemeResource(mTheme, mThemeResource, first); 217 } 218 } 219 220