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