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