Home | History | Annotate | Download | only in view
      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.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      */
     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 
    220