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