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