| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.layoutlib.bridge.impl; |
| |
| import static com.android.ide.common.rendering.api.Result.Status.ERROR_LOCK_INTERRUPTED; |
| import static com.android.ide.common.rendering.api.Result.Status.ERROR_TIMEOUT; |
| import static com.android.ide.common.rendering.api.Result.Status.SUCCESS; |
| |
| import com.android.ide.common.rendering.api.HardwareConfig; |
| import com.android.ide.common.rendering.api.LayoutLog; |
| import com.android.ide.common.rendering.api.RenderParams; |
| import com.android.ide.common.rendering.api.RenderResources; |
| import com.android.ide.common.rendering.api.RenderResources.FrameworkResourceIdProvider; |
| import com.android.ide.common.rendering.api.Result; |
| import com.android.layoutlib.bridge.Bridge; |
| import com.android.layoutlib.bridge.android.BridgeContext; |
| import com.android.resources.Density; |
| import com.android.resources.ResourceType; |
| import com.android.resources.ScreenOrientation; |
| import com.android.resources.ScreenSize; |
| |
| import android.content.res.Configuration; |
| import android.os.HandlerThread_Delegate; |
| import android.os.Looper; |
| import android.util.DisplayMetrics; |
| import android.view.ViewConfiguration_Accessor; |
| import android.view.inputmethod.InputMethodManager; |
| import android.view.inputmethod.InputMethodManager_Accessor; |
| |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.locks.ReentrantLock; |
| |
| /** |
| * Base class for rendering action. |
| * |
| * It provides life-cycle methods to init and stop the rendering. |
| * The most important methods are: |
| * {@link #init(long)} and {@link #acquire(long)} to start a rendering and {@link #release()} |
| * after the rendering. |
| * |
| * |
| * @param <T> the {@link RenderParams} implementation |
| * |
| */ |
| public abstract class RenderAction<T extends RenderParams> extends FrameworkResourceIdProvider { |
| |
| /** |
| * The current context being rendered. This is set through {@link #acquire(long)} and |
| * {@link #init(long)}, and unset in {@link #release()}. |
| */ |
| private static BridgeContext sCurrentContext = null; |
| |
| private final T mParams; |
| |
| private BridgeContext mContext; |
| |
| /** |
| * Creates a renderAction. |
| * <p> |
| * This <b>must</b> be followed by a call to {@link RenderAction#init()}, which act as a |
| * call to {@link RenderAction#acquire(long)} |
| * |
| * @param params the RenderParams. This must be a copy that the action can keep |
| * |
| */ |
| protected RenderAction(T params) { |
| mParams = params; |
| } |
| |
| /** |
| * Initializes and acquires the scene, creating various Android objects such as context, |
| * inflater, and parser. |
| * |
| * @param timeout the time to wait if another rendering is happening. |
| * |
| * @return whether the scene was prepared |
| * |
| * @see #acquire(long) |
| * @see #release() |
| */ |
| public Result init(long timeout) { |
| // acquire the lock. if the result is null, lock was just acquired, otherwise, return |
| // the result. |
| Result result = acquireLock(timeout); |
| if (result != null) { |
| return result; |
| } |
| |
| HardwareConfig hardwareConfig = mParams.getHardwareConfig(); |
| |
| // setup the display Metrics. |
| DisplayMetrics metrics = new DisplayMetrics(); |
| metrics.densityDpi = metrics.noncompatDensityDpi = |
| hardwareConfig.getDensity().getDpiValue(); |
| |
| metrics.density = metrics.noncompatDensity = |
| metrics.densityDpi / (float) DisplayMetrics.DENSITY_DEFAULT; |
| |
| metrics.scaledDensity = metrics.noncompatScaledDensity = metrics.density; |
| |
| metrics.widthPixels = metrics.noncompatWidthPixels = hardwareConfig.getScreenWidth(); |
| metrics.heightPixels = metrics.noncompatHeightPixels = hardwareConfig.getScreenHeight(); |
| metrics.xdpi = metrics.noncompatXdpi = hardwareConfig.getXdpi(); |
| metrics.ydpi = metrics.noncompatYdpi = hardwareConfig.getYdpi(); |
| |
| RenderResources resources = mParams.getResources(); |
| |
| // build the context |
| mContext = new BridgeContext(mParams.getProjectKey(), metrics, resources, |
| mParams.getProjectCallback(), getConfiguration(), mParams.getTargetSdkVersion()); |
| |
| setUp(); |
| |
| return SUCCESS.createResult(); |
| } |
| |
| |
| /** |
| * Prepares the scene for action. |
| * <p> |
| * This call is blocking if another rendering/inflating is currently happening, and will return |
| * whether the preparation worked. |
| * |
| * The preparation can fail if another rendering took too long and the timeout was elapsed. |
| * |
| * More than one call to this from the same thread will have no effect and will return |
| * {@link Result#SUCCESS}. |
| * |
| * After scene actions have taken place, only one call to {@link #release()} must be |
| * done. |
| * |
| * @param timeout the time to wait if another rendering is happening. |
| * |
| * @return whether the scene was prepared |
| * |
| * @see #release() |
| * |
| * @throws IllegalStateException if {@link #init(long)} was never called. |
| */ |
| public Result acquire(long timeout) { |
| if (mContext == null) { |
| throw new IllegalStateException("After scene creation, #init() must be called"); |
| } |
| |
| // acquire the lock. if the result is null, lock was just acquired, otherwise, return |
| // the result. |
| Result result = acquireLock(timeout); |
| if (result != null) { |
| return result; |
| } |
| |
| setUp(); |
| |
| return SUCCESS.createResult(); |
| } |
| |
| /** |
| * Acquire the lock so that the scene can be acted upon. |
| * <p> |
| * This returns null if the lock was just acquired, otherwise it returns |
| * {@link Result#SUCCESS} if the lock already belonged to that thread, or another |
| * instance (see {@link Result#getStatus()}) if an error occurred. |
| * |
| * @param timeout the time to wait if another rendering is happening. |
| * @return null if the lock was just acquire or another result depending on the state. |
| * |
| * @throws IllegalStateException if the current context is different than the one owned by |
| * the scene. |
| */ |
| private Result acquireLock(long timeout) { |
| ReentrantLock lock = Bridge.getLock(); |
| if (lock.isHeldByCurrentThread() == false) { |
| try { |
| boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS); |
| |
| if (acquired == false) { |
| return ERROR_TIMEOUT.createResult(); |
| } |
| } catch (InterruptedException e) { |
| return ERROR_LOCK_INTERRUPTED.createResult(); |
| } |
| } else { |
| // This thread holds the lock already. Checks that this wasn't for a different context. |
| // If this is called by init, mContext will be null and so should sCurrentContext |
| // anyway |
| if (mContext != sCurrentContext) { |
| throw new IllegalStateException("Acquiring different scenes from same thread without releases"); |
| } |
| return SUCCESS.createResult(); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Cleans up the scene after an action. |
| */ |
| public void release() { |
| ReentrantLock lock = Bridge.getLock(); |
| |
| // with the use of finally blocks, it is possible to find ourself calling this |
| // without a successful call to prepareScene. This test makes sure that unlock() will |
| // not throw IllegalMonitorStateException. |
| if (lock.isHeldByCurrentThread()) { |
| tearDown(); |
| lock.unlock(); |
| } |
| } |
| |
| /** |
| * Sets up the session for rendering. |
| * <p/> |
| * The counterpart is {@link #tearDown()}. |
| */ |
| private void setUp() { |
| // make sure the Resources object references the context (and other objects) for this |
| // scene |
| mContext.initResources(); |
| sCurrentContext = mContext; |
| |
| // create an InputMethodManager |
| InputMethodManager.getInstance(); |
| |
| LayoutLog currentLog = mParams.getLog(); |
| Bridge.setLog(currentLog); |
| mContext.getRenderResources().setFrameworkResourceIdProvider(this); |
| mContext.getRenderResources().setLogger(currentLog); |
| } |
| |
| /** |
| * Tear down the session after rendering. |
| * <p/> |
| * The counterpart is {@link #setUp()}. |
| */ |
| private void tearDown() { |
| // Make sure to remove static references, otherwise we could not unload the lib |
| mContext.disposeResources(); |
| |
| // quit HandlerThread created during this session. |
| HandlerThread_Delegate.cleanUp(sCurrentContext); |
| |
| // clear the stored ViewConfiguration since the map is per density and not per context. |
| ViewConfiguration_Accessor.clearConfigurations(); |
| |
| // remove the InputMethodManager |
| InputMethodManager_Accessor.resetInstance(); |
| |
| sCurrentContext = null; |
| |
| Bridge.setLog(null); |
| mContext.getRenderResources().setFrameworkResourceIdProvider(null); |
| mContext.getRenderResources().setLogger(null); |
| } |
| |
| public static BridgeContext getCurrentContext() { |
| return sCurrentContext; |
| } |
| |
| protected T getParams() { |
| return mParams; |
| } |
| |
| protected BridgeContext getContext() { |
| return mContext; |
| } |
| |
| /** |
| * Returns the log associated with the session. |
| * @return the log or null if there are none. |
| */ |
| public LayoutLog getLog() { |
| if (mParams != null) { |
| return mParams.getLog(); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Checks that the lock is owned by the current thread and that the current context is the one |
| * from this scene. |
| * |
| * @throws IllegalStateException if the current context is different than the one owned by |
| * the scene, or if {@link #acquire(long)} was not called. |
| */ |
| protected void checkLock() { |
| ReentrantLock lock = Bridge.getLock(); |
| if (lock.isHeldByCurrentThread() == false) { |
| throw new IllegalStateException("scene must be acquired first. see #acquire(long)"); |
| } |
| if (sCurrentContext != mContext) { |
| throw new IllegalStateException("Thread acquired a scene but is rendering a different one"); |
| } |
| } |
| |
| private Configuration getConfiguration() { |
| Configuration config = new Configuration(); |
| |
| HardwareConfig hardwareConfig = mParams.getHardwareConfig(); |
| |
| ScreenSize screenSize = hardwareConfig.getScreenSize(); |
| if (screenSize != null) { |
| switch (screenSize) { |
| case SMALL: |
| config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_SMALL; |
| break; |
| case NORMAL: |
| config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_NORMAL; |
| break; |
| case LARGE: |
| config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_LARGE; |
| break; |
| case XLARGE: |
| config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_XLARGE; |
| break; |
| } |
| } |
| |
| Density density = hardwareConfig.getDensity(); |
| if (density == null) { |
| density = Density.MEDIUM; |
| } |
| |
| config.screenWidthDp = hardwareConfig.getScreenWidth() / density.getDpiValue(); |
| config.screenHeightDp = hardwareConfig.getScreenHeight() / density.getDpiValue(); |
| if (config.screenHeightDp < config.screenWidthDp) { |
| config.smallestScreenWidthDp = config.screenHeightDp; |
| } else { |
| config.smallestScreenWidthDp = config.screenWidthDp; |
| } |
| config.densityDpi = density.getDpiValue(); |
| |
| // never run in compat mode: |
| config.compatScreenWidthDp = config.screenWidthDp; |
| config.compatScreenHeightDp = config.screenHeightDp; |
| |
| ScreenOrientation orientation = hardwareConfig.getOrientation(); |
| if (orientation != null) { |
| switch (orientation) { |
| case PORTRAIT: |
| config.orientation = Configuration.ORIENTATION_PORTRAIT; |
| break; |
| case LANDSCAPE: |
| config.orientation = Configuration.ORIENTATION_LANDSCAPE; |
| break; |
| case SQUARE: |
| config.orientation = Configuration.ORIENTATION_SQUARE; |
| break; |
| } |
| } else { |
| config.orientation = Configuration.ORIENTATION_UNDEFINED; |
| } |
| |
| // TODO: fill in more config info. |
| |
| return config; |
| } |
| |
| |
| // --- FrameworkResourceIdProvider methods |
| |
| @Override |
| public Integer getId(ResourceType resType, String resName) { |
| return Bridge.getResourceId(resType, resName); |
| } |
| } |