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