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