blob: b909bec6299efd8b2b694f47d764961f0fca987d [file] [log] [blame]
/*
* 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);
}
}