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