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