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