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 17 package com.android.layoutlib.bridge.impl; 18 19 import com.android.ide.common.rendering.api.HardwareConfig; 20 import com.android.ide.common.rendering.api.LayoutLog; 21 import com.android.ide.common.rendering.api.RenderParams; 22 import com.android.ide.common.rendering.api.RenderResources; 23 import com.android.ide.common.rendering.api.Result; 24 import com.android.layoutlib.bridge.Bridge; 25 import com.android.layoutlib.bridge.android.BridgeContext; 26 import com.android.resources.Density; 27 import com.android.resources.ScreenOrientation; 28 import com.android.resources.ScreenRound; 29 import com.android.resources.ScreenSize; 30 import com.android.tools.layoutlib.annotations.VisibleForTesting; 31 32 import android.animation.PropertyValuesHolder_Accessor; 33 import android.content.res.Configuration; 34 import android.os.HandlerThread_Delegate; 35 import android.util.DisplayMetrics; 36 import android.view.IWindowManager; 37 import android.view.IWindowManagerImpl; 38 import android.view.Surface; 39 import android.view.ViewConfiguration_Accessor; 40 import android.view.WindowManagerGlobal_Delegate; 41 import android.view.inputmethod.InputMethodManager_Accessor; 42 43 import java.util.Locale; 44 import java.util.concurrent.TimeUnit; 45 import java.util.concurrent.locks.ReentrantLock; 46 47 import static com.android.ide.common.rendering.api.Result.Status.ERROR_LOCK_INTERRUPTED; 48 import static com.android.ide.common.rendering.api.Result.Status.ERROR_TIMEOUT; 49 import static com.android.ide.common.rendering.api.Result.Status.SUCCESS; 50 51 /** 52 * Base class for rendering action. 53 * 54 * It provides life-cycle methods to init and stop the rendering. 55 * The most important methods are: 56 * {@link #init(long)} and {@link #acquire(long)} to start a rendering and {@link #release()} 57 * after the rendering. 58 * 59 * 60 * @param <T> the {@link RenderParams} implementation 61 * 62 */ 63 public abstract class RenderAction<T extends RenderParams> { 64 65 /** 66 * The current context being rendered. This is set through {@link #acquire(long)} and 67 * {@link #init(long)}, and unset in {@link #release()}. 68 */ 69 @VisibleForTesting 70 static BridgeContext sCurrentContext = null; 71 72 private final T mParams; 73 74 private BridgeContext mContext; 75 76 /** 77 * Creates a renderAction. 78 * <p> 79 * This <b>must</b> be followed by a call to {@link RenderAction#init(long)}, which act as a 80 * call to {@link RenderAction#acquire(long)} 81 * 82 * @param params the RenderParams. This must be a copy that the action can keep 83 * 84 */ RenderAction(T params)85 protected RenderAction(T params) { 86 mParams = params; 87 } 88 89 /** 90 * Initializes and acquires the scene, creating various Android objects such as context, 91 * inflater, and parser. 92 * 93 * @param timeout the time to wait if another rendering is happening. 94 * 95 * @return whether the scene was prepared 96 * 97 * @see #acquire(long) 98 * @see #release() 99 */ init(long timeout)100 public Result init(long timeout) { 101 // acquire the lock. if the result is null, lock was just acquired, otherwise, return 102 // the result. 103 Result result = acquireLock(timeout); 104 if (result != null) { 105 return result; 106 } 107 108 HardwareConfig hardwareConfig = mParams.getHardwareConfig(); 109 110 // setup the display Metrics. 111 DisplayMetrics metrics = new DisplayMetrics(); 112 metrics.densityDpi = metrics.noncompatDensityDpi = 113 hardwareConfig.getDensity().getDpiValue(); 114 115 metrics.density = metrics.noncompatDensity = 116 metrics.densityDpi / (float) DisplayMetrics.DENSITY_DEFAULT; 117 118 metrics.scaledDensity = metrics.noncompatScaledDensity = metrics.density; 119 120 metrics.widthPixels = metrics.noncompatWidthPixels = hardwareConfig.getScreenWidth(); 121 metrics.heightPixels = metrics.noncompatHeightPixels = hardwareConfig.getScreenHeight(); 122 metrics.xdpi = metrics.noncompatXdpi = hardwareConfig.getXdpi(); 123 metrics.ydpi = metrics.noncompatYdpi = hardwareConfig.getYdpi(); 124 125 RenderResources resources = mParams.getResources(); 126 127 // build the context 128 mContext = new BridgeContext(mParams.getProjectKey(), metrics, resources, 129 mParams.getAssets(), mParams.getLayoutlibCallback(), getConfiguration(mParams), 130 mParams.getTargetSdkVersion(), mParams.isRtlSupported()); 131 132 setUp(); 133 134 return SUCCESS.createResult(); 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 */ acquire(long timeout)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 */ acquireLock(long timeout)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 */ release()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 */ setUp()234 private void setUp() { 235 // setup the ParserFactory 236 ParserFactory.setParserFactory(mParams.getLayoutlibCallback()); 237 238 // make sure the Resources object references the context (and other objects) for this 239 // scene 240 mContext.initResources(); 241 sCurrentContext = mContext; 242 243 // Set-up WindowManager 244 // FIXME: find those out, and possibly add them to the render params 245 boolean hasNavigationBar = true; 246 //noinspection ConstantConditions 247 IWindowManager iwm = new IWindowManagerImpl(getContext().getConfiguration(), 248 getContext().getMetrics(), Surface.ROTATION_0, hasNavigationBar); 249 WindowManagerGlobal_Delegate.setWindowManagerService(iwm); 250 251 LayoutLog currentLog = mParams.getLog(); 252 Bridge.setLog(currentLog); 253 mContext.getRenderResources().setLogger(currentLog); 254 } 255 256 /** 257 * Tear down the session after rendering. 258 * <p/> 259 * The counterpart is {@link #setUp()}. 260 */ tearDown()261 private void tearDown() { 262 // The context may be null, if there was an error during init(). 263 if (mContext != null) { 264 // Make sure to remove static references, otherwise we could not unload the lib 265 mContext.disposeResources(); 266 } 267 268 if (sCurrentContext != null) { 269 // quit HandlerThread created during this session. 270 HandlerThread_Delegate.cleanUp(sCurrentContext); 271 } 272 273 // clear the stored ViewConfiguration since the map is per density and not per context. 274 ViewConfiguration_Accessor.clearConfigurations(); 275 276 // remove the InputMethodManager 277 InputMethodManager_Accessor.tearDownEditMode(); 278 279 sCurrentContext = null; 280 281 Bridge.setLog(null); 282 if (mContext != null) { 283 mContext.getRenderResources().setLogger(null); 284 } 285 ParserFactory.setParserFactory(null); 286 287 PropertyValuesHolder_Accessor.clearClassCaches(); 288 } 289 getCurrentContext()290 public static BridgeContext getCurrentContext() { 291 return sCurrentContext; 292 } 293 getParams()294 protected T getParams() { 295 return mParams; 296 } 297 getContext()298 protected BridgeContext getContext() { 299 return mContext; 300 } 301 302 /** 303 * Returns the log associated with the session. 304 * @return the log or null if there are none. 305 */ getLog()306 public LayoutLog getLog() { 307 if (mParams != null) { 308 return mParams.getLog(); 309 } 310 311 return null; 312 } 313 314 /** 315 * Checks that the lock is owned by the current thread and that the current context is the one 316 * from this scene. 317 * 318 * @throws IllegalStateException if the current context is different than the one owned by 319 * the scene, or if {@link #acquire(long)} was not called. 320 */ checkLock()321 protected void checkLock() { 322 ReentrantLock lock = Bridge.getLock(); 323 if (!lock.isHeldByCurrentThread()) { 324 throw new IllegalStateException("scene must be acquired first. see #acquire(long)"); 325 } 326 if (sCurrentContext != mContext) { 327 throw new IllegalStateException("Thread acquired a scene but is rendering a different one"); 328 } 329 } 330 331 // VisibleForTesting getConfiguration(RenderParams params)332 public static Configuration getConfiguration(RenderParams params) { 333 Configuration config = new Configuration(); 334 335 HardwareConfig hardwareConfig = params.getHardwareConfig(); 336 337 ScreenSize screenSize = hardwareConfig.getScreenSize(); 338 if (screenSize != null) { 339 switch (screenSize) { 340 case SMALL: 341 config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_SMALL; 342 break; 343 case NORMAL: 344 config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_NORMAL; 345 break; 346 case LARGE: 347 config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_LARGE; 348 break; 349 case XLARGE: 350 config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_XLARGE; 351 break; 352 } 353 } 354 355 Density density = hardwareConfig.getDensity(); 356 if (density == null) { 357 density = Density.MEDIUM; 358 } 359 360 config.screenWidthDp = hardwareConfig.getScreenWidth() * 160 / density.getDpiValue(); 361 config.screenHeightDp = hardwareConfig.getScreenHeight() * 160 / density.getDpiValue(); 362 if (config.screenHeightDp < config.screenWidthDp) { 363 //noinspection SuspiciousNameCombination 364 config.smallestScreenWidthDp = config.screenHeightDp; 365 } else { 366 config.smallestScreenWidthDp = config.screenWidthDp; 367 } 368 config.densityDpi = density.getDpiValue(); 369 370 // never run in compat mode: 371 config.compatScreenWidthDp = config.screenWidthDp; 372 config.compatScreenHeightDp = config.screenHeightDp; 373 374 ScreenOrientation orientation = hardwareConfig.getOrientation(); 375 if (orientation != null) { 376 switch (orientation) { 377 case PORTRAIT: 378 config.orientation = Configuration.ORIENTATION_PORTRAIT; 379 break; 380 case LANDSCAPE: 381 config.orientation = Configuration.ORIENTATION_LANDSCAPE; 382 break; 383 case SQUARE: 384 //noinspection deprecation 385 config.orientation = Configuration.ORIENTATION_SQUARE; 386 break; 387 } 388 } else { 389 config.orientation = Configuration.ORIENTATION_UNDEFINED; 390 } 391 392 ScreenRound roundness = hardwareConfig.getScreenRoundness(); 393 if (roundness != null) { 394 switch (roundness) { 395 case ROUND: 396 config.screenLayout |= Configuration.SCREENLAYOUT_ROUND_YES; 397 break; 398 case NOTROUND: 399 config.screenLayout |= Configuration.SCREENLAYOUT_ROUND_NO; 400 } 401 } else { 402 config.screenLayout |= Configuration.SCREENLAYOUT_ROUND_UNDEFINED; 403 } 404 String locale = params.getLocale(); 405 if (locale != null && !locale.isEmpty()) config.locale = new Locale(locale); 406 407 // TODO: fill in more config info. 408 409 return config; 410 } 411 } 412