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