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
17package android.view;
18
19import android.annotation.StyleRes;
20import android.content.Context;
21import android.content.ContextWrapper;
22import android.content.res.AssetManager;
23import android.content.res.Configuration;
24import 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 */
30public 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     */
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     */
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     */
73    public ContextThemeWrapper(Context base, Resources.Theme theme) {
74        super(base);
75        mTheme = theme;
76    }
77
78    @Override
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     */
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     */
109    public Configuration getOverrideConfiguration() {
110        return mOverrideConfiguration;
111    }
112
113    @Override
114    public AssetManager getAssets() {
115        // Ensure we're returning assets with the correct configuration.
116        return getResourcesInternal().getAssets();
117    }
118
119    @Override
120    public Resources getResources() {
121        return getResourcesInternal();
122    }
123
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
137    public void setTheme(int resid) {
138        if (mThemeResource != resid) {
139            mThemeResource = resid;
140            initializeTheme();
141        }
142    }
143
144    /** @hide */
145    @Override
146    public int getThemeResId() {
147        return mThemeResource;
148    }
149
150    @Override
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
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     */
185    protected void onApplyThemeResource(Resources.Theme theme, int resId, boolean first) {
186        theme.applyStyle(resId, true);
187    }
188
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