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