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.StyleRes; 20 import android.content.Context; 21 import android.content.ContextWrapper; 22 import android.content.res.AssetManager; 23 import android.content.res.Configuration; 24 import android.content.res.Resources; 25 26 /** 27 * A context wrapper that allows you to modify or replace the theme of the 28 * wrapped context. 29 */ 30 public class ContextThemeWrapper extends ContextWrapper { 31 private int mThemeResource; 32 private Resources.Theme mTheme; 33 private LayoutInflater mInflater; 34 private Configuration mOverrideConfiguration; 35 private Resources mResources; 36 37 /** 38 * Creates a new context wrapper with no theme and no base context. 39 * <p class="note"> 40 * <strong>Note:</strong> A base context <strong>must</strong> be attached 41 * using {@link #attachBaseContext(Context)} before calling any other 42 * method on the newly constructed context wrapper. 43 */ ContextThemeWrapper()44 public ContextThemeWrapper() { 45 super(null); 46 } 47 48 /** 49 * Creates a new context wrapper with the specified theme. 50 * <p> 51 * The specified theme will be applied on top of the base context's theme. 52 * Any attributes not explicitly defined in the theme identified by 53 * <var>themeResId</var> will retain their original values. 54 * 55 * @param base the base context 56 * @param themeResId the resource ID of the theme to be applied on top of 57 * the base context's theme 58 */ ContextThemeWrapper(Context base, @StyleRes int themeResId)59 public ContextThemeWrapper(Context base, @StyleRes int themeResId) { 60 super(base); 61 mThemeResource = themeResId; 62 } 63 64 /** 65 * Creates a new context wrapper with the specified theme. 66 * <p> 67 * Unlike {@link #ContextThemeWrapper(Context, int)}, the theme passed to 68 * this constructor will completely replace the base context's theme. 69 * 70 * @param base the base context 71 * @param theme the theme against which resources should be inflated 72 */ ContextThemeWrapper(Context base, Resources.Theme theme)73 public ContextThemeWrapper(Context base, Resources.Theme theme) { 74 super(base); 75 mTheme = theme; 76 } 77 78 @Override attachBaseContext(Context newBase)79 protected void attachBaseContext(Context newBase) { 80 super.attachBaseContext(newBase); 81 } 82 83 /** 84 * Call to set an "override configuration" on this context -- this is 85 * a configuration that replies one or more values of the standard 86 * configuration that is applied to the context. See 87 * {@link Context#createConfigurationContext(Configuration)} for more 88 * information. 89 * 90 * <p>This method can only be called once, and must be called before any 91 * calls to {@link #getResources()} or {@link #getAssets()} are made. 92 */ applyOverrideConfiguration(Configuration overrideConfiguration)93 public void applyOverrideConfiguration(Configuration overrideConfiguration) { 94 if (mResources != null) { 95 throw new IllegalStateException( 96 "getResources() or getAssets() has already been called"); 97 } 98 if (mOverrideConfiguration != null) { 99 throw new IllegalStateException("Override configuration has already been set"); 100 } 101 mOverrideConfiguration = new Configuration(overrideConfiguration); 102 } 103 104 /** 105 * Used by ActivityThread to apply the overridden configuration to onConfigurationChange 106 * callbacks. 107 * @hide 108 */ getOverrideConfiguration()109 public Configuration getOverrideConfiguration() { 110 return mOverrideConfiguration; 111 } 112 113 @Override getAssets()114 public AssetManager getAssets() { 115 // Ensure we're returning assets with the correct configuration. 116 return getResourcesInternal().getAssets(); 117 } 118 119 @Override getResources()120 public Resources getResources() { 121 return getResourcesInternal(); 122 } 123 getResourcesInternal()124 private Resources getResourcesInternal() { 125 if (mResources == null) { 126 if (mOverrideConfiguration == null) { 127 mResources = super.getResources(); 128 } else { 129 final Context resContext = createConfigurationContext(mOverrideConfiguration); 130 mResources = resContext.getResources(); 131 } 132 } 133 return mResources; 134 } 135 136 @Override setTheme(int resid)137 public void setTheme(int resid) { 138 if (mThemeResource != resid) { 139 mThemeResource = resid; 140 initializeTheme(); 141 } 142 } 143 144 /** @hide */ 145 @Override getThemeResId()146 public int getThemeResId() { 147 return mThemeResource; 148 } 149 150 @Override getTheme()151 public Resources.Theme getTheme() { 152 if (mTheme != null) { 153 return mTheme; 154 } 155 156 mThemeResource = Resources.selectDefaultTheme(mThemeResource, 157 getApplicationInfo().targetSdkVersion); 158 initializeTheme(); 159 160 return mTheme; 161 } 162 163 @Override getSystemService(String name)164 public Object getSystemService(String name) { 165 if (LAYOUT_INFLATER_SERVICE.equals(name)) { 166 if (mInflater == null) { 167 mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this); 168 } 169 return mInflater; 170 } 171 return getBaseContext().getSystemService(name); 172 } 173 174 /** 175 * Called by {@link #setTheme} and {@link #getTheme} to apply a theme 176 * resource to the current Theme object. May be overridden to change the 177 * default (simple) behavior. This method will not be called in multiple 178 * threads simultaneously. 179 * 180 * @param theme the theme being modified 181 * @param resId the style resource being applied to <var>theme</var> 182 * @param first {@code true} if this is the first time a style is being 183 * applied to <var>theme</var> 184 */ onApplyThemeResource(Resources.Theme theme, int resId, boolean first)185 protected void onApplyThemeResource(Resources.Theme theme, int resId, boolean first) { 186 theme.applyStyle(resId, true); 187 } 188 initializeTheme()189 private void initializeTheme() { 190 final boolean first = mTheme == null; 191 if (first) { 192 mTheme = getResources().newTheme(); 193 final Resources.Theme theme = getBaseContext().getTheme(); 194 if (theme != null) { 195 mTheme.setTo(theme); 196 } 197 } 198 onApplyThemeResource(mTheme, mThemeResource, first); 199 } 200 } 201 202