RenderAction.java revision fd18f573280bbbcc549b35b548580a562bd960e2
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.ResourceType; 31 32import android.util.DisplayMetrics; 33 34import java.util.concurrent.TimeUnit; 35import java.util.concurrent.locks.ReentrantLock; 36 37/** 38 * Base class for rendering action. 39 * 40 * It provides life-cycle methods to init and stop the rendering. 41 * The most important methods are: 42 * {@link #init(long)} and {@link #acquire(long)} to start a rendering and {@link #release()} 43 * after the rendering. 44 * 45 * 46 * @param <T> the {@link RenderParams} implementation 47 * 48 */ 49public abstract class RenderAction<T extends RenderParams> extends FrameworkResourceIdProvider { 50 51 /** 52 * The current context being rendered. This is set through {@link #acquire(long)} and 53 * {@link #init(long)}, and unset in {@link #release()}. 54 */ 55 private static BridgeContext sCurrentContext = null; 56 57 private final T mParams; 58 59 private BridgeContext mContext; 60 61 /** 62 * Creates a renderAction. 63 * <p> 64 * This <b>must</b> be followed by a call to {@link RenderAction#init()}, which act as a 65 * call to {@link RenderAction#acquire(long)} 66 * 67 * @param params the RenderParams. This must be a copy that the action can keep 68 * 69 */ 70 protected RenderAction(T params) { 71 mParams = params; 72 } 73 74 /** 75 * Initializes and acquires the scene, creating various Android objects such as context, 76 * inflater, and parser. 77 * 78 * @param timeout the time to wait if another rendering is happening. 79 * 80 * @return whether the scene was prepared 81 * 82 * @see #acquire(long) 83 * @see #release() 84 */ 85 public Result init(long timeout) { 86 // acquire the lock. if the result is null, lock was just acquired, otherwise, return 87 // the result. 88 Result result = acquireLock(timeout); 89 if (result != null) { 90 return result; 91 } 92 93 // setup the display Metrics. 94 DisplayMetrics metrics = new DisplayMetrics(); 95 metrics.densityDpi = mParams.getDensity().getDpiValue(); 96 metrics.density = metrics.densityDpi / (float) DisplayMetrics.DENSITY_DEFAULT; 97 metrics.scaledDensity = metrics.density; 98 metrics.widthPixels = mParams.getScreenWidth(); 99 metrics.heightPixels = mParams.getScreenHeight(); 100 metrics.xdpi = mParams.getXdpi(); 101 metrics.ydpi = mParams.getYdpi(); 102 103 RenderResources resources = mParams.getResources(); 104 105 // build the context 106 mContext = new BridgeContext(mParams.getProjectKey(), metrics, resources, 107 mParams.getProjectCallback(), mParams.getTargetSdkVersion()); 108 109 setUp(); 110 111 return SUCCESS.createResult(); 112 } 113 114 /** 115 * Prepares the scene for action. 116 * <p> 117 * This call is blocking if another rendering/inflating is currently happening, and will return 118 * whether the preparation worked. 119 * 120 * The preparation can fail if another rendering took too long and the timeout was elapsed. 121 * 122 * More than one call to this from the same thread will have no effect and will return 123 * {@link Result#SUCCESS}. 124 * 125 * After scene actions have taken place, only one call to {@link #release()} must be 126 * done. 127 * 128 * @param timeout the time to wait if another rendering is happening. 129 * 130 * @return whether the scene was prepared 131 * 132 * @see #release() 133 * 134 * @throws IllegalStateException if {@link #init(long)} was never called. 135 */ 136 public Result acquire(long timeout) { 137 if (mContext == null) { 138 throw new IllegalStateException("After scene creation, #init() must be called"); 139 } 140 141 // acquire the lock. if the result is null, lock was just acquired, otherwise, return 142 // the result. 143 Result result = acquireLock(timeout); 144 if (result != null) { 145 return result; 146 } 147 148 setUp(); 149 150 return SUCCESS.createResult(); 151 } 152 153 /** 154 * Acquire the lock so that the scene can be acted upon. 155 * <p> 156 * This returns null if the lock was just acquired, otherwise it returns 157 * {@link Result#SUCCESS} if the lock already belonged to that thread, or another 158 * instance (see {@link Result#getStatus()}) if an error occurred. 159 * 160 * @param timeout the time to wait if another rendering is happening. 161 * @return null if the lock was just acquire or another result depending on the state. 162 * 163 * @throws IllegalStateException if the current context is different than the one owned by 164 * the scene. 165 */ 166 private Result acquireLock(long timeout) { 167 ReentrantLock lock = Bridge.getLock(); 168 if (lock.isHeldByCurrentThread() == false) { 169 try { 170 boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS); 171 172 if (acquired == false) { 173 return ERROR_TIMEOUT.createResult(); 174 } 175 } catch (InterruptedException e) { 176 return ERROR_LOCK_INTERRUPTED.createResult(); 177 } 178 } else { 179 // This thread holds the lock already. Checks that this wasn't for a different context. 180 // If this is called by init, mContext will be null and so should sCurrentContext 181 // anyway 182 if (mContext != sCurrentContext) { 183 throw new IllegalStateException("Acquiring different scenes from same thread without releases"); 184 } 185 return SUCCESS.createResult(); 186 } 187 188 return null; 189 } 190 191 /** 192 * Cleans up the scene after an action. 193 */ 194 public void release() { 195 ReentrantLock lock = Bridge.getLock(); 196 197 // with the use of finally blocks, it is possible to find ourself calling this 198 // without a successful call to prepareScene. This test makes sure that unlock() will 199 // not throw IllegalMonitorStateException. 200 if (lock.isHeldByCurrentThread()) { 201 tearDown(); 202 lock.unlock(); 203 } 204 } 205 206 /** 207 * Sets up the session for rendering. 208 * <p/> 209 * The counterpart is {@link #tearDown()}. 210 */ 211 private void setUp() { 212 // make sure the Resources object references the context (and other objects) for this 213 // scene 214 mContext.initResources(); 215 sCurrentContext = mContext; 216 217 LayoutLog currentLog = mParams.getLog(); 218 Bridge.setLog(currentLog); 219 mContext.getRenderResources().setFrameworkResourceIdProvider(this); 220 mContext.getRenderResources().setLogger(currentLog); 221 } 222 223 /** 224 * Tear down the session after rendering. 225 * <p/> 226 * The counterpart is {@link #setUp()}. 227 */ 228 private void tearDown() { 229 // Make sure to remove static references, otherwise we could not unload the lib 230 mContext.disposeResources(); 231 sCurrentContext = null; 232 233 Bridge.setLog(null); 234 mContext.getRenderResources().setFrameworkResourceIdProvider(null); 235 mContext.getRenderResources().setLogger(null); 236 } 237 238 public static BridgeContext getCurrentContext() { 239 return sCurrentContext; 240 } 241 242 protected T getParams() { 243 return mParams; 244 } 245 246 protected BridgeContext getContext() { 247 return mContext; 248 } 249 250 /** 251 * Returns the log associated with the session. 252 * @return the log or null if there are none. 253 */ 254 public LayoutLog getLog() { 255 if (mParams != null) { 256 return mParams.getLog(); 257 } 258 259 return null; 260 } 261 262 /** 263 * Checks that the lock is owned by the current thread and that the current context is the one 264 * from this scene. 265 * 266 * @throws IllegalStateException if the current context is different than the one owned by 267 * the scene, or if {@link #acquire(long)} was not called. 268 */ 269 protected void checkLock() { 270 ReentrantLock lock = Bridge.getLock(); 271 if (lock.isHeldByCurrentThread() == false) { 272 throw new IllegalStateException("scene must be acquired first. see #acquire(long)"); 273 } 274 if (sCurrentContext != mContext) { 275 throw new IllegalStateException("Thread acquired a scene but is rendering a different one"); 276 } 277 } 278 279 // --- FrameworkResourceIdProvider methods 280 281 @Override 282 public Integer getId(ResourceType resType, String resName) { 283 return Bridge.getResourceId(resType, resName); 284 } 285} 286