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