RenderAction.java revision 5c90c4f459c2a7213a197f7ee98ad71e85af31bc
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 com.android.ide.common.rendering.api.HardwareConfig;
20import com.android.ide.common.rendering.api.LayoutLog;
21import com.android.ide.common.rendering.api.RenderParams;
22import com.android.ide.common.rendering.api.RenderResources;
23import com.android.ide.common.rendering.api.RenderResources.FrameworkResourceIdProvider;
24import com.android.ide.common.rendering.api.Result;
25import com.android.layoutlib.bridge.Bridge;
26import com.android.layoutlib.bridge.android.BridgeContext;
27import com.android.resources.Density;
28import com.android.resources.ResourceType;
29import com.android.resources.ScreenOrientation;
30import com.android.resources.ScreenSize;
31
32import android.content.res.Configuration;
33import android.os.HandlerThread_Delegate;
34import android.util.DisplayMetrics;
35import android.view.ViewConfiguration_Accessor;
36import android.view.inputmethod.InputMethodManager;
37import android.view.inputmethod.InputMethodManager_Accessor;
38
39import java.util.concurrent.TimeUnit;
40import java.util.concurrent.locks.ReentrantLock;
41
42import javax.swing.text.html.parser.Parser;
43
44import static com.android.ide.common.rendering.api.Result.Status.ERROR_LOCK_INTERRUPTED;
45import static com.android.ide.common.rendering.api.Result.Status.ERROR_TIMEOUT;
46import 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 */
60public 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     */
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     */
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        // setup the ParserFactory
105        ParserFactory.setLayoutlibCallback(mParams.getLayoutlibCallback());
106
107        HardwareConfig hardwareConfig = mParams.getHardwareConfig();
108
109        // setup the display Metrics.
110        DisplayMetrics metrics = new DisplayMetrics();
111        metrics.densityDpi = metrics.noncompatDensityDpi =
112                hardwareConfig.getDensity().getDpiValue();
113
114        metrics.density = metrics.noncompatDensity =
115                metrics.densityDpi / (float) DisplayMetrics.DENSITY_DEFAULT;
116
117        metrics.scaledDensity = metrics.noncompatScaledDensity = metrics.density;
118
119        metrics.widthPixels = metrics.noncompatWidthPixels = hardwareConfig.getScreenWidth();
120        metrics.heightPixels = metrics.noncompatHeightPixels = hardwareConfig.getScreenHeight();
121        metrics.xdpi = metrics.noncompatXdpi = hardwareConfig.getXdpi();
122        metrics.ydpi = metrics.noncompatYdpi = hardwareConfig.getYdpi();
123
124        RenderResources resources = mParams.getResources();
125
126        // build the context
127        mContext = new BridgeContext(mParams.getProjectKey(), metrics, resources,
128                mParams.getAssets(), mParams.getLayoutlibCallback(), getConfiguration(),
129                mParams.getTargetSdkVersion(), mParams.isRtlSupported());
130
131        setUp();
132
133        return SUCCESS.createResult();
134    }
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     */
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     */
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     */
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     */
234    private void setUp() {
235        // make sure the Resources object references the context (and other objects) for this
236        // scene
237        mContext.initResources();
238        sCurrentContext = mContext;
239
240        // create an InputMethodManager
241        InputMethodManager.getInstance();
242
243        LayoutLog currentLog = mParams.getLog();
244        Bridge.setLog(currentLog);
245        mContext.getRenderResources().setFrameworkResourceIdProvider(this);
246        mContext.getRenderResources().setLogger(currentLog);
247    }
248
249    /**
250     * Tear down the session after rendering.
251     * <p/>
252     * The counterpart is {@link #setUp()}.
253     */
254    private void tearDown() {
255        // The context may be null, if there was an error during init().
256        if (mContext != null) {
257            // Make sure to remove static references, otherwise we could not unload the lib
258            mContext.disposeResources();
259        }
260
261        if (sCurrentContext != null) {
262            // quit HandlerThread created during this session.
263            HandlerThread_Delegate.cleanUp(sCurrentContext);
264        }
265
266        // clear the stored ViewConfiguration since the map is per density and not per context.
267        ViewConfiguration_Accessor.clearConfigurations();
268
269        // remove the InputMethodManager
270        InputMethodManager_Accessor.resetInstance();
271
272        sCurrentContext = null;
273
274        Bridge.setLog(null);
275        if (mContext != null) {
276            mContext.getRenderResources().setFrameworkResourceIdProvider(null);
277            mContext.getRenderResources().setLogger(null);
278        }
279        ParserFactory.setLayoutlibCallback(null);
280
281    }
282
283    public static BridgeContext getCurrentContext() {
284        return sCurrentContext;
285    }
286
287    protected T getParams() {
288        return mParams;
289    }
290
291    protected BridgeContext getContext() {
292        return mContext;
293    }
294
295    /**
296     * Returns the log associated with the session.
297     * @return the log or null if there are none.
298     */
299    public LayoutLog getLog() {
300        if (mParams != null) {
301            return mParams.getLog();
302        }
303
304        return null;
305    }
306
307    /**
308     * Checks that the lock is owned by the current thread and that the current context is the one
309     * from this scene.
310     *
311     * @throws IllegalStateException if the current context is different than the one owned by
312     *      the scene, or if {@link #acquire(long)} was not called.
313     */
314    protected void checkLock() {
315        ReentrantLock lock = Bridge.getLock();
316        if (!lock.isHeldByCurrentThread()) {
317            throw new IllegalStateException("scene must be acquired first. see #acquire(long)");
318        }
319        if (sCurrentContext != mContext) {
320            throw new IllegalStateException("Thread acquired a scene but is rendering a different one");
321        }
322    }
323
324    private Configuration getConfiguration() {
325        Configuration config = new Configuration();
326
327        HardwareConfig hardwareConfig = mParams.getHardwareConfig();
328
329        ScreenSize screenSize = hardwareConfig.getScreenSize();
330        if (screenSize != null) {
331            switch (screenSize) {
332                case SMALL:
333                    config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_SMALL;
334                    break;
335                case NORMAL:
336                    config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_NORMAL;
337                    break;
338                case LARGE:
339                    config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_LARGE;
340                    break;
341                case XLARGE:
342                    config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_XLARGE;
343                    break;
344            }
345        }
346
347        Density density = hardwareConfig.getDensity();
348        if (density == null) {
349            density = Density.MEDIUM;
350        }
351
352        config.screenWidthDp = hardwareConfig.getScreenWidth() / density.getDpiValue();
353        config.screenHeightDp = hardwareConfig.getScreenHeight() / density.getDpiValue();
354        if (config.screenHeightDp < config.screenWidthDp) {
355            //noinspection SuspiciousNameCombination
356            config.smallestScreenWidthDp = config.screenHeightDp;
357        } else {
358            config.smallestScreenWidthDp = config.screenWidthDp;
359        }
360        config.densityDpi = density.getDpiValue();
361
362        // never run in compat mode:
363        config.compatScreenWidthDp = config.screenWidthDp;
364        config.compatScreenHeightDp = config.screenHeightDp;
365
366        ScreenOrientation orientation = hardwareConfig.getOrientation();
367        if (orientation != null) {
368            switch (orientation) {
369            case PORTRAIT:
370                config.orientation = Configuration.ORIENTATION_PORTRAIT;
371                break;
372            case LANDSCAPE:
373                config.orientation = Configuration.ORIENTATION_LANDSCAPE;
374                break;
375            case SQUARE:
376                //noinspection deprecation
377                config.orientation = Configuration.ORIENTATION_SQUARE;
378                break;
379            }
380        } else {
381            config.orientation = Configuration.ORIENTATION_UNDEFINED;
382        }
383
384        // TODO: fill in more config info.
385
386        return config;
387    }
388
389
390    // --- FrameworkResourceIdProvider methods
391
392    @Override
393    public Integer getId(ResourceType resType, String resName) {
394        return Bridge.getResourceId(resType, resName);
395    }
396}
397