Home | History | Annotate | Download | only in impl
      1 /*
      2  * Copyright (C) 2010 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 com.android.layoutlib.bridge.impl;
     18 
     19 import com.android.ide.common.rendering.api.HardwareConfig;
     20 import com.android.ide.common.rendering.api.LayoutLog;
     21 import com.android.ide.common.rendering.api.RenderParams;
     22 import com.android.ide.common.rendering.api.RenderResources;
     23 import com.android.ide.common.rendering.api.RenderResources.FrameworkResourceIdProvider;
     24 import com.android.ide.common.rendering.api.Result;
     25 import com.android.layoutlib.bridge.Bridge;
     26 import com.android.layoutlib.bridge.android.BridgeContext;
     27 import com.android.resources.Density;
     28 import com.android.resources.ResourceType;
     29 import com.android.resources.ScreenOrientation;
     30 import com.android.resources.ScreenRound;
     31 import com.android.resources.ScreenSize;
     32 import com.android.tools.layoutlib.annotations.VisibleForTesting;
     33 
     34 import android.content.res.Configuration;
     35 import android.os.HandlerThread_Delegate;
     36 import android.util.DisplayMetrics;
     37 import android.view.IWindowManager;
     38 import android.view.IWindowManagerImpl;
     39 import android.view.Surface;
     40 import android.view.ViewConfiguration_Accessor;
     41 import android.view.WindowManagerGlobal_Delegate;
     42 import android.view.inputmethod.InputMethodManager;
     43 import android.view.inputmethod.InputMethodManager_Accessor;
     44 
     45 import java.util.Locale;
     46 import java.util.concurrent.TimeUnit;
     47 import java.util.concurrent.locks.ReentrantLock;
     48 
     49 import static com.android.ide.common.rendering.api.Result.Status.ERROR_LOCK_INTERRUPTED;
     50 import static com.android.ide.common.rendering.api.Result.Status.ERROR_TIMEOUT;
     51 import static com.android.ide.common.rendering.api.Result.Status.SUCCESS;
     52 
     53 /**
     54  * Base class for rendering action.
     55  *
     56  * It provides life-cycle methods to init and stop the rendering.
     57  * The most important methods are:
     58  * {@link #init(long)} and {@link #acquire(long)} to start a rendering and {@link #release()}
     59  * after the rendering.
     60  *
     61  *
     62  * @param <T> the {@link RenderParams} implementation
     63  *
     64  */
     65 public abstract class RenderAction<T extends RenderParams> extends FrameworkResourceIdProvider {
     66 
     67     /**
     68      * The current context being rendered. This is set through {@link #acquire(long)} and
     69      * {@link #init(long)}, and unset in {@link #release()}.
     70      */
     71     @VisibleForTesting
     72     static BridgeContext sCurrentContext = null;
     73 
     74     private final T mParams;
     75 
     76     private BridgeContext mContext;
     77 
     78     /**
     79      * Creates a renderAction.
     80      * <p>
     81      * This <b>must</b> be followed by a call to {@link RenderAction#init(long)}, which act as a
     82      * call to {@link RenderAction#acquire(long)}
     83      *
     84      * @param params the RenderParams. This must be a copy that the action can keep
     85      *
     86      */
     87     protected RenderAction(T params) {
     88         mParams = params;
     89     }
     90 
     91     /**
     92      * Initializes and acquires the scene, creating various Android objects such as context,
     93      * inflater, and parser.
     94      *
     95      * @param timeout the time to wait if another rendering is happening.
     96      *
     97      * @return whether the scene was prepared
     98      *
     99      * @see #acquire(long)
    100      * @see #release()
    101      */
    102     public Result init(long timeout) {
    103         // acquire the lock. if the result is null, lock was just acquired, otherwise, return
    104         // the result.
    105         Result result = acquireLock(timeout);
    106         if (result != null) {
    107             return result;
    108         }
    109 
    110         HardwareConfig hardwareConfig = mParams.getHardwareConfig();
    111 
    112         // setup the display Metrics.
    113         DisplayMetrics metrics = new DisplayMetrics();
    114         metrics.densityDpi = metrics.noncompatDensityDpi =
    115                 hardwareConfig.getDensity().getDpiValue();
    116 
    117         metrics.density = metrics.noncompatDensity =
    118                 metrics.densityDpi / (float) DisplayMetrics.DENSITY_DEFAULT;
    119 
    120         metrics.scaledDensity = metrics.noncompatScaledDensity = metrics.density;
    121 
    122         metrics.widthPixels = metrics.noncompatWidthPixels = hardwareConfig.getScreenWidth();
    123         metrics.heightPixels = metrics.noncompatHeightPixels = hardwareConfig.getScreenHeight();
    124         metrics.xdpi = metrics.noncompatXdpi = hardwareConfig.getXdpi();
    125         metrics.ydpi = metrics.noncompatYdpi = hardwareConfig.getYdpi();
    126 
    127         RenderResources resources = mParams.getResources();
    128 
    129         // build the context
    130         mContext = new BridgeContext(mParams.getProjectKey(), metrics, resources,
    131                 mParams.getAssets(), mParams.getLayoutlibCallback(), getConfiguration(mParams),
    132                 mParams.getTargetSdkVersion(), mParams.isRtlSupported());
    133 
    134         setUp();
    135 
    136         return SUCCESS.createResult();
    137     }
    138 
    139     /**
    140      * Prepares the scene for action.
    141      * <p>
    142      * This call is blocking if another rendering/inflating is currently happening, and will return
    143      * whether the preparation worked.
    144      *
    145      * The preparation can fail if another rendering took too long and the timeout was elapsed.
    146      *
    147      * More than one call to this from the same thread will have no effect and will return
    148      * {@link Result.Status#SUCCESS}.
    149      *
    150      * After scene actions have taken place, only one call to {@link #release()} must be
    151      * done.
    152      *
    153      * @param timeout the time to wait if another rendering is happening.
    154      *
    155      * @return whether the scene was prepared
    156      *
    157      * @see #release()
    158      *
    159      * @throws IllegalStateException if {@link #init(long)} was never called.
    160      */
    161     public Result acquire(long timeout) {
    162         if (mContext == null) {
    163             throw new IllegalStateException("After scene creation, #init() must be called");
    164         }
    165 
    166         // acquire the lock. if the result is null, lock was just acquired, otherwise, return
    167         // the result.
    168         Result result = acquireLock(timeout);
    169         if (result != null) {
    170             return result;
    171         }
    172 
    173         setUp();
    174 
    175         return SUCCESS.createResult();
    176     }
    177 
    178     /**
    179      * Acquire the lock so that the scene can be acted upon.
    180      * <p>
    181      * This returns null if the lock was just acquired, otherwise it returns
    182      * {@link Result.Status#SUCCESS} if the lock already belonged to that thread, or another
    183      * instance (see {@link Result#getStatus()}) if an error occurred.
    184      *
    185      * @param timeout the time to wait if another rendering is happening.
    186      * @return null if the lock was just acquire or another result depending on the state.
    187      *
    188      * @throws IllegalStateException if the current context is different than the one owned by
    189      *      the scene.
    190      */
    191     private Result acquireLock(long timeout) {
    192         ReentrantLock lock = Bridge.getLock();
    193         if (!lock.isHeldByCurrentThread()) {
    194             try {
    195                 boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS);
    196 
    197                 if (!acquired) {
    198                     return ERROR_TIMEOUT.createResult();
    199                 }
    200             } catch (InterruptedException e) {
    201                 return ERROR_LOCK_INTERRUPTED.createResult();
    202             }
    203         } else {
    204             // This thread holds the lock already. Checks that this wasn't for a different context.
    205             // If this is called by init, mContext will be null and so should sCurrentContext
    206             // anyway
    207             if (mContext != sCurrentContext) {
    208                 throw new IllegalStateException("Acquiring different scenes from same thread without releases");
    209             }
    210             return SUCCESS.createResult();
    211         }
    212 
    213         return null;
    214     }
    215 
    216     /**
    217      * Cleans up the scene after an action.
    218      */
    219     public void release() {
    220         ReentrantLock lock = Bridge.getLock();
    221 
    222         // with the use of finally blocks, it is possible to find ourself calling this
    223         // without a successful call to prepareScene. This test makes sure that unlock() will
    224         // not throw IllegalMonitorStateException.
    225         if (lock.isHeldByCurrentThread()) {
    226             tearDown();
    227             lock.unlock();
    228         }
    229     }
    230 
    231     /**
    232      * Sets up the session for rendering.
    233      * <p/>
    234      * The counterpart is {@link #tearDown()}.
    235      */
    236     private void setUp() {
    237         // setup the ParserFactory
    238         ParserFactory.setParserFactory(mParams.getLayoutlibCallback().getParserFactory());
    239 
    240         // make sure the Resources object references the context (and other objects) for this
    241         // scene
    242         mContext.initResources();
    243         sCurrentContext = mContext;
    244 
    245         // create an InputMethodManager
    246         InputMethodManager.getInstance();
    247 
    248         // Set-up WindowManager
    249         // FIXME: find those out, and possibly add them to the render params
    250         boolean hasNavigationBar = true;
    251         //noinspection ConstantConditions
    252         IWindowManager iwm = new IWindowManagerImpl(getContext().getConfiguration(),
    253                 getContext().getMetrics(), Surface.ROTATION_0, hasNavigationBar);
    254         WindowManagerGlobal_Delegate.setWindowManagerService(iwm);
    255 
    256         LayoutLog currentLog = mParams.getLog();
    257         Bridge.setLog(currentLog);
    258         mContext.getRenderResources().setFrameworkResourceIdProvider(this);
    259         mContext.getRenderResources().setLogger(currentLog);
    260     }
    261 
    262     /**
    263      * Tear down the session after rendering.
    264      * <p/>
    265      * The counterpart is {@link #setUp()}.
    266      */
    267     private void tearDown() {
    268         // The context may be null, if there was an error during init().
    269         if (mContext != null) {
    270             // Make sure to remove static references, otherwise we could not unload the lib
    271             mContext.disposeResources();
    272         }
    273 
    274         if (sCurrentContext != null) {
    275             // quit HandlerThread created during this session.
    276             HandlerThread_Delegate.cleanUp(sCurrentContext);
    277         }
    278 
    279         // clear the stored ViewConfiguration since the map is per density and not per context.
    280         ViewConfiguration_Accessor.clearConfigurations();
    281 
    282         // remove the InputMethodManager
    283         InputMethodManager_Accessor.resetInstance();
    284 
    285         sCurrentContext = null;
    286 
    287         Bridge.setLog(null);
    288         if (mContext != null) {
    289             mContext.getRenderResources().setFrameworkResourceIdProvider(null);
    290             mContext.getRenderResources().setLogger(null);
    291         }
    292         ParserFactory.setParserFactory(null);
    293     }
    294 
    295     public static BridgeContext getCurrentContext() {
    296         return sCurrentContext;
    297     }
    298 
    299     protected T getParams() {
    300         return mParams;
    301     }
    302 
    303     protected BridgeContext getContext() {
    304         return mContext;
    305     }
    306 
    307     /**
    308      * Returns the log associated with the session.
    309      * @return the log or null if there are none.
    310      */
    311     public LayoutLog getLog() {
    312         if (mParams != null) {
    313             return mParams.getLog();
    314         }
    315 
    316         return null;
    317     }
    318 
    319     /**
    320      * Checks that the lock is owned by the current thread and that the current context is the one
    321      * from this scene.
    322      *
    323      * @throws IllegalStateException if the current context is different than the one owned by
    324      *      the scene, or if {@link #acquire(long)} was not called.
    325      */
    326     protected void checkLock() {
    327         ReentrantLock lock = Bridge.getLock();
    328         if (!lock.isHeldByCurrentThread()) {
    329             throw new IllegalStateException("scene must be acquired first. see #acquire(long)");
    330         }
    331         if (sCurrentContext != mContext) {
    332             throw new IllegalStateException("Thread acquired a scene but is rendering a different one");
    333         }
    334     }
    335 
    336     // VisibleForTesting
    337     public static Configuration getConfiguration(RenderParams params) {
    338         Configuration config = new Configuration();
    339 
    340         HardwareConfig hardwareConfig = params.getHardwareConfig();
    341 
    342         ScreenSize screenSize = hardwareConfig.getScreenSize();
    343         if (screenSize != null) {
    344             switch (screenSize) {
    345                 case SMALL:
    346                     config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_SMALL;
    347                     break;
    348                 case NORMAL:
    349                     config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_NORMAL;
    350                     break;
    351                 case LARGE:
    352                     config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_LARGE;
    353                     break;
    354                 case XLARGE:
    355                     config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_XLARGE;
    356                     break;
    357             }
    358         }
    359 
    360         Density density = hardwareConfig.getDensity();
    361         if (density == null) {
    362             density = Density.MEDIUM;
    363         }
    364 
    365         config.screenWidthDp = hardwareConfig.getScreenWidth() / density.getDpiValue();
    366         config.screenHeightDp = hardwareConfig.getScreenHeight() / density.getDpiValue();
    367         if (config.screenHeightDp < config.screenWidthDp) {
    368             //noinspection SuspiciousNameCombination
    369             config.smallestScreenWidthDp = config.screenHeightDp;
    370         } else {
    371             config.smallestScreenWidthDp = config.screenWidthDp;
    372         }
    373         config.densityDpi = density.getDpiValue();
    374 
    375         // never run in compat mode:
    376         config.compatScreenWidthDp = config.screenWidthDp;
    377         config.compatScreenHeightDp = config.screenHeightDp;
    378 
    379         ScreenOrientation orientation = hardwareConfig.getOrientation();
    380         if (orientation != null) {
    381             switch (orientation) {
    382             case PORTRAIT:
    383                 config.orientation = Configuration.ORIENTATION_PORTRAIT;
    384                 break;
    385             case LANDSCAPE:
    386                 config.orientation = Configuration.ORIENTATION_LANDSCAPE;
    387                 break;
    388             case SQUARE:
    389                 //noinspection deprecation
    390                 config.orientation = Configuration.ORIENTATION_SQUARE;
    391                 break;
    392             }
    393         } else {
    394             config.orientation = Configuration.ORIENTATION_UNDEFINED;
    395         }
    396 
    397         ScreenRound roundness = hardwareConfig.getScreenRoundness();
    398         if (roundness != null) {
    399             switch (roundness) {
    400                 case ROUND:
    401                     config.screenLayout |= Configuration.SCREENLAYOUT_ROUND_YES;
    402                     break;
    403                 case NOTROUND:
    404                     config.screenLayout |= Configuration.SCREENLAYOUT_ROUND_NO;
    405             }
    406         } else {
    407             config.screenLayout |= Configuration.SCREENLAYOUT_ROUND_UNDEFINED;
    408         }
    409         String locale = params.getLocale();
    410         if (locale != null && !locale.isEmpty()) config.locale = new Locale(locale);
    411 
    412         // TODO: fill in more config info.
    413 
    414         return config;
    415     }
    416 
    417 
    418     // --- FrameworkResourceIdProvider methods
    419 
    420     @Override
    421     public Integer getId(ResourceType resType, String resName) {
    422         return Bridge.getResourceId(resType, resName);
    423     }
    424 }
    425