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