blob: 876331b5c57f292a2f0985ac3354be2ba6dc7460 [file] [log] [blame]
Aurimas Liutikas93554f22022-04-19 16:51:35 -07001/*
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
17package android.view;
18
19import android.annotation.Nullable;
20import android.annotation.StyleRes;
21import android.compat.annotation.UnsupportedAppUsage;
22import android.content.Context;
23import android.content.ContextWrapper;
24import android.content.res.AssetManager;
25import android.content.res.Configuration;
26import android.content.res.Resources;
27import android.os.Build;
28
29/**
30 * A context wrapper that allows you to modify or replace the theme of the
31 * wrapped context.
32 */
33public 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 */
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 */
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 */
80 public ContextThemeWrapper(Context base, Resources.Theme theme) {
81 super(base);
82 mTheme = theme;
83 }
84
85 @Override
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 */
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 */
116 public Configuration getOverrideConfiguration() {
117 return mOverrideConfiguration;
118 }
119
120 @Override
121 public AssetManager getAssets() {
122 // Ensure we're returning assets with the correct configuration.
123 return getResourcesInternal().getAssets();
124 }
125
126 @Override
127 public Resources getResources() {
128 return getResourcesInternal();
129 }
130
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
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 */
156 public void setTheme(@Nullable Resources.Theme theme) {
157 mTheme = theme;
158 }
159
160 /** @hide */
161 @Override
162 @UnsupportedAppUsage
163 public int getThemeResId() {
164 return mThemeResource;
165 }
166
167 @Override
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
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 */
202 protected void onApplyThemeResource(Resources.Theme theme, int resId, boolean first) {
203 theme.applyStyle(resId, true);
204 }
205
206 @UnsupportedAppUsage
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