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