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