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