Home | History | Annotate | Download | only in app
      1 /*
      2  * Copyright (C) 2013 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.app;
     18 
     19 import static android.app.ActivityThread.DEBUG_CONFIGURATION;
     20 
     21 import android.content.pm.ActivityInfo;
     22 import android.content.res.AssetManager;
     23 import android.content.res.CompatibilityInfo;
     24 import android.content.res.Configuration;
     25 import android.content.res.Resources;
     26 import android.content.res.ResourcesKey;
     27 import android.hardware.display.DisplayManagerGlobal;
     28 import android.os.IBinder;
     29 import android.util.ArrayMap;
     30 import android.util.DisplayMetrics;
     31 import android.util.Slog;
     32 import android.view.Display;
     33 import android.view.DisplayAdjustments;
     34 
     35 import java.lang.ref.WeakReference;
     36 import java.util.Locale;
     37 
     38 /** @hide */
     39 public class ResourcesManager {
     40     static final String TAG = "ResourcesManager";
     41     static final boolean DEBUG_CACHE = false;
     42     static final boolean DEBUG_STATS = true;
     43 
     44     private static ResourcesManager sResourcesManager;
     45     final ArrayMap<ResourcesKey, WeakReference<Resources> > mActiveResources
     46             = new ArrayMap<ResourcesKey, WeakReference<Resources> >();
     47 
     48     final ArrayMap<DisplayAdjustments, DisplayMetrics> mDefaultDisplayMetrics
     49             = new ArrayMap<DisplayAdjustments, DisplayMetrics>();
     50 
     51     CompatibilityInfo mResCompatibilityInfo;
     52 
     53     Configuration mResConfiguration;
     54     final Configuration mTmpConfig = new Configuration();
     55 
     56     public static ResourcesManager getInstance() {
     57         synchronized (ResourcesManager.class) {
     58             if (sResourcesManager == null) {
     59                 sResourcesManager = new ResourcesManager();
     60             }
     61             return sResourcesManager;
     62         }
     63     }
     64 
     65     public Configuration getConfiguration() {
     66         return mResConfiguration;
     67     }
     68 
     69     public void flushDisplayMetricsLocked() {
     70         mDefaultDisplayMetrics.clear();
     71     }
     72 
     73     public DisplayMetrics getDisplayMetricsLocked(int displayId) {
     74         return getDisplayMetricsLocked(displayId, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
     75     }
     76 
     77     public DisplayMetrics getDisplayMetricsLocked(int displayId, DisplayAdjustments daj) {
     78         boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
     79         DisplayMetrics dm = isDefaultDisplay ? mDefaultDisplayMetrics.get(daj) : null;
     80         if (dm != null) {
     81             return dm;
     82         }
     83         dm = new DisplayMetrics();
     84 
     85         DisplayManagerGlobal displayManager = DisplayManagerGlobal.getInstance();
     86         if (displayManager == null) {
     87             // may be null early in system startup
     88             dm.setToDefaults();
     89             return dm;
     90         }
     91 
     92         if (isDefaultDisplay) {
     93             mDefaultDisplayMetrics.put(daj, dm);
     94         }
     95 
     96         Display d = displayManager.getCompatibleDisplay(displayId, daj);
     97         if (d != null) {
     98             d.getMetrics(dm);
     99         } else {
    100             // Display no longer exists
    101             // FIXME: This would not be a problem if we kept the Display object around
    102             // instead of using the raw display id everywhere.  The Display object caches
    103             // its information even after the display has been removed.
    104             dm.setToDefaults();
    105         }
    106         //Slog.i("foo", "New metrics: w=" + metrics.widthPixels + " h="
    107         //        + metrics.heightPixels + " den=" + metrics.density
    108         //        + " xdpi=" + metrics.xdpi + " ydpi=" + metrics.ydpi);
    109         return dm;
    110     }
    111 
    112     final void applyNonDefaultDisplayMetricsToConfigurationLocked(
    113             DisplayMetrics dm, Configuration config) {
    114         config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
    115         config.densityDpi = dm.densityDpi;
    116         config.screenWidthDp = (int)(dm.widthPixels / dm.density);
    117         config.screenHeightDp = (int)(dm.heightPixels / dm.density);
    118         int sl = Configuration.resetScreenLayout(config.screenLayout);
    119         if (dm.widthPixels > dm.heightPixels) {
    120             config.orientation = Configuration.ORIENTATION_LANDSCAPE;
    121             config.screenLayout = Configuration.reduceScreenLayout(sl,
    122                     config.screenWidthDp, config.screenHeightDp);
    123         } else {
    124             config.orientation = Configuration.ORIENTATION_PORTRAIT;
    125             config.screenLayout = Configuration.reduceScreenLayout(sl,
    126                     config.screenHeightDp, config.screenWidthDp);
    127         }
    128         config.smallestScreenWidthDp = config.screenWidthDp; // assume screen does not rotate
    129         config.compatScreenWidthDp = config.screenWidthDp;
    130         config.compatScreenHeightDp = config.screenHeightDp;
    131         config.compatSmallestScreenWidthDp = config.smallestScreenWidthDp;
    132     }
    133 
    134     public boolean applyCompatConfiguration(int displayDensity,
    135             Configuration compatConfiguration) {
    136         if (mResCompatibilityInfo != null && !mResCompatibilityInfo.supportsScreen()) {
    137             mResCompatibilityInfo.applyToConfiguration(displayDensity, compatConfiguration);
    138             return true;
    139         }
    140         return false;
    141     }
    142 
    143     /**
    144      * Creates the top level Resources for applications with the given compatibility info.
    145      *
    146      * @param resDir the resource directory.
    147      * @param overlayDirs the resource overlay directories.
    148      * @param libDirs the shared library resource dirs this app references.
    149      * @param compatInfo the compability info. Must not be null.
    150      * @param token the application token for determining stack bounds.
    151      */
    152     public Resources getTopLevelResources(String resDir, String[] splitResDirs,
    153             String[] overlayDirs, String[] libDirs, int displayId,
    154             Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) {
    155         final float scale = compatInfo.applicationScale;
    156         ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale, token);
    157         Resources r;
    158         synchronized (this) {
    159             // Resources is app scale dependent.
    160             if (false) {
    161                 Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale);
    162             }
    163             WeakReference<Resources> wr = mActiveResources.get(key);
    164             r = wr != null ? wr.get() : null;
    165             //if (r != null) Slog.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());
    166             if (r != null && r.getAssets().isUpToDate()) {
    167                 if (false) {
    168                     Slog.w(TAG, "Returning cached resources " + r + " " + resDir
    169                             + ": appScale=" + r.getCompatibilityInfo().applicationScale);
    170                 }
    171                 return r;
    172             }
    173         }
    174 
    175         //if (r != null) {
    176         //    Slog.w(TAG, "Throwing away out-of-date resources!!!! "
    177         //            + r + " " + resDir);
    178         //}
    179 
    180         AssetManager assets = new AssetManager();
    181         // resDir can be null if the 'android' package is creating a new Resources object.
    182         // This is fine, since each AssetManager automatically loads the 'android' package
    183         // already.
    184         if (resDir != null) {
    185             if (assets.addAssetPath(resDir) == 0) {
    186                 return null;
    187             }
    188         }
    189 
    190         if (splitResDirs != null) {
    191             for (String splitResDir : splitResDirs) {
    192                 if (assets.addAssetPath(splitResDir) == 0) {
    193                     return null;
    194                 }
    195             }
    196         }
    197 
    198         if (overlayDirs != null) {
    199             for (String idmapPath : overlayDirs) {
    200                 assets.addOverlayPath(idmapPath);
    201             }
    202         }
    203 
    204         if (libDirs != null) {
    205             for (String libDir : libDirs) {
    206                 if (assets.addAssetPath(libDir) == 0) {
    207                     Slog.w(TAG, "Asset path '" + libDir +
    208                             "' does not exist or contains no resources.");
    209                 }
    210             }
    211         }
    212 
    213         //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
    214         DisplayMetrics dm = getDisplayMetricsLocked(displayId);
    215         Configuration config;
    216         boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
    217         final boolean hasOverrideConfig = key.hasOverrideConfiguration();
    218         if (!isDefaultDisplay || hasOverrideConfig) {
    219             config = new Configuration(getConfiguration());
    220             if (!isDefaultDisplay) {
    221                 applyNonDefaultDisplayMetricsToConfigurationLocked(dm, config);
    222             }
    223             if (hasOverrideConfig) {
    224                 config.updateFrom(key.mOverrideConfiguration);
    225             }
    226         } else {
    227             config = getConfiguration();
    228         }
    229         r = new Resources(assets, dm, config, compatInfo, token);
    230         if (false) {
    231             Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "
    232                     + r.getConfiguration() + " appScale="
    233                     + r.getCompatibilityInfo().applicationScale);
    234         }
    235 
    236         synchronized (this) {
    237             WeakReference<Resources> wr = mActiveResources.get(key);
    238             Resources existing = wr != null ? wr.get() : null;
    239             if (existing != null && existing.getAssets().isUpToDate()) {
    240                 // Someone else already created the resources while we were
    241                 // unlocked; go ahead and use theirs.
    242                 r.getAssets().close();
    243                 return existing;
    244             }
    245 
    246             // XXX need to remove entries when weak references go away
    247             mActiveResources.put(key, new WeakReference<Resources>(r));
    248             return r;
    249         }
    250     }
    251 
    252     public final boolean applyConfigurationToResourcesLocked(Configuration config,
    253             CompatibilityInfo compat) {
    254         if (mResConfiguration == null) {
    255             mResConfiguration = new Configuration();
    256         }
    257         if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) {
    258             if (DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq="
    259                     + mResConfiguration.seq + ", newSeq=" + config.seq);
    260             return false;
    261         }
    262         int changes = mResConfiguration.updateFrom(config);
    263         flushDisplayMetricsLocked();
    264         DisplayMetrics defaultDisplayMetrics = getDisplayMetricsLocked(Display.DEFAULT_DISPLAY);
    265 
    266         if (compat != null && (mResCompatibilityInfo == null ||
    267                 !mResCompatibilityInfo.equals(compat))) {
    268             mResCompatibilityInfo = compat;
    269             changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT
    270                     | ActivityInfo.CONFIG_SCREEN_SIZE
    271                     | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
    272         }
    273 
    274         // set it for java, this also affects newly created Resources
    275         if (config.locale != null) {
    276             Locale.setDefault(config.locale);
    277         }
    278 
    279         Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat);
    280 
    281         ApplicationPackageManager.configurationChanged();
    282         //Slog.i(TAG, "Configuration changed in " + currentPackageName());
    283 
    284         Configuration tmpConfig = null;
    285 
    286         for (int i=mActiveResources.size()-1; i>=0; i--) {
    287             ResourcesKey key = mActiveResources.keyAt(i);
    288             Resources r = mActiveResources.valueAt(i).get();
    289             if (r != null) {
    290                 if (DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "
    291                         + r + " config to: " + config);
    292                 int displayId = key.mDisplayId;
    293                 boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
    294                 DisplayMetrics dm = defaultDisplayMetrics;
    295                 final boolean hasOverrideConfiguration = key.hasOverrideConfiguration();
    296                 if (!isDefaultDisplay || hasOverrideConfiguration) {
    297                     if (tmpConfig == null) {
    298                         tmpConfig = new Configuration();
    299                     }
    300                     tmpConfig.setTo(config);
    301                     if (!isDefaultDisplay) {
    302                         dm = getDisplayMetricsLocked(displayId);
    303                         applyNonDefaultDisplayMetricsToConfigurationLocked(dm, tmpConfig);
    304                     }
    305                     if (hasOverrideConfiguration) {
    306                         tmpConfig.updateFrom(key.mOverrideConfiguration);
    307                     }
    308                     r.updateConfiguration(tmpConfig, dm, compat);
    309                 } else {
    310                     r.updateConfiguration(config, dm, compat);
    311                 }
    312                 //Slog.i(TAG, "Updated app resources " + v.getKey()
    313                 //        + " " + r + ": " + r.getConfiguration());
    314             } else {
    315                 //Slog.i(TAG, "Removing old resources " + v.getKey());
    316                 mActiveResources.removeAt(i);
    317             }
    318         }
    319 
    320         return changes != 0;
    321     }
    322 
    323 }
    324