RenderAction.java revision 37dbb8b7f3c069196040eed3a03006647db7fa5b
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 com.android.ide.common.rendering.api.HardwareConfig;
20import com.android.ide.common.rendering.api.LayoutLog;
21import com.android.ide.common.rendering.api.RenderParams;
22import com.android.ide.common.rendering.api.RenderResources;
23import com.android.ide.common.rendering.api.RenderResources.FrameworkResourceIdProvider;
24import com.android.ide.common.rendering.api.Result;
25import com.android.layoutlib.bridge.Bridge;
26import com.android.layoutlib.bridge.android.BridgeContext;
27import com.android.resources.Density;
28import com.android.resources.ResourceType;
29import com.android.resources.ScreenOrientation;
30import com.android.resources.ScreenSize;
31
32import android.content.res.Configuration;
33import android.os.HandlerThread_Delegate;
34import android.util.DisplayMetrics;
35import android.view.ViewConfiguration_Accessor;
36import android.view.inputmethod.InputMethodManager;
37import android.view.inputmethod.InputMethodManager_Accessor;
38
39import java.util.concurrent.TimeUnit;
40import java.util.concurrent.locks.ReentrantLock;
41
42import static com.android.ide.common.rendering.api.Result.Status.ERROR_LOCK_INTERRUPTED;
43import static com.android.ide.common.rendering.api.Result.Status.ERROR_TIMEOUT;
44import static com.android.ide.common.rendering.api.Result.Status.SUCCESS;
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(long)}, 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 = metrics.noncompatDensityDpi =
107                hardwareConfig.getDensity().getDpiValue();
108
109        metrics.density = metrics.noncompatDensity =
110                metrics.densityDpi / (float) DisplayMetrics.DENSITY_DEFAULT;
111
112        metrics.scaledDensity = metrics.noncompatScaledDensity = metrics.density;
113
114        metrics.widthPixels = metrics.noncompatWidthPixels = hardwareConfig.getScreenWidth();
115        metrics.heightPixels = metrics.noncompatHeightPixels = hardwareConfig.getScreenHeight();
116        metrics.xdpi = metrics.noncompatXdpi = hardwareConfig.getXdpi();
117        metrics.ydpi = metrics.noncompatYdpi = hardwareConfig.getYdpi();
118
119        RenderResources resources = mParams.getResources();
120
121        // build the context
122        mContext = new BridgeContext(mParams.getProjectKey(), metrics, resources,
123                mParams.getAssets(), mParams.getLayoutlibCallback(), getConfiguration(),
124                mParams.getTargetSdkVersion(), mParams.isRtlSupported());
125
126        setUp();
127
128        return SUCCESS.createResult();
129    }
130
131
132    /**
133     * Prepares the scene for action.
134     * <p>
135     * This call is blocking if another rendering/inflating is currently happening, and will return
136     * whether the preparation worked.
137     *
138     * The preparation can fail if another rendering took too long and the timeout was elapsed.
139     *
140     * More than one call to this from the same thread will have no effect and will return
141     * {@link Result.Status#SUCCESS}.
142     *
143     * After scene actions have taken place, only one call to {@link #release()} must be
144     * done.
145     *
146     * @param timeout the time to wait if another rendering is happening.
147     *
148     * @return whether the scene was prepared
149     *
150     * @see #release()
151     *
152     * @throws IllegalStateException if {@link #init(long)} was never called.
153     */
154    public Result acquire(long timeout) {
155        if (mContext == null) {
156            throw new IllegalStateException("After scene creation, #init() must be called");
157        }
158
159        // acquire the lock. if the result is null, lock was just acquired, otherwise, return
160        // the result.
161        Result result = acquireLock(timeout);
162        if (result != null) {
163            return result;
164        }
165
166        setUp();
167
168        return SUCCESS.createResult();
169    }
170
171    /**
172     * Acquire the lock so that the scene can be acted upon.
173     * <p>
174     * This returns null if the lock was just acquired, otherwise it returns
175     * {@link Result.Status#SUCCESS} if the lock already belonged to that thread, or another
176     * instance (see {@link Result#getStatus()}) if an error occurred.
177     *
178     * @param timeout the time to wait if another rendering is happening.
179     * @return null if the lock was just acquire or another result depending on the state.
180     *
181     * @throws IllegalStateException if the current context is different than the one owned by
182     *      the scene.
183     */
184    private Result acquireLock(long timeout) {
185        ReentrantLock lock = Bridge.getLock();
186        if (!lock.isHeldByCurrentThread()) {
187            try {
188                boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS);
189
190                if (!acquired) {
191                    return ERROR_TIMEOUT.createResult();
192                }
193            } catch (InterruptedException e) {
194                return ERROR_LOCK_INTERRUPTED.createResult();
195            }
196        } else {
197            // This thread holds the lock already. Checks that this wasn't for a different context.
198            // If this is called by init, mContext will be null and so should sCurrentContext
199            // anyway
200            if (mContext != sCurrentContext) {
201                throw new IllegalStateException("Acquiring different scenes from same thread without releases");
202            }
203            return SUCCESS.createResult();
204        }
205
206        return null;
207    }
208
209    /**
210     * Cleans up the scene after an action.
211     */
212    public void release() {
213        ReentrantLock lock = Bridge.getLock();
214
215        // with the use of finally blocks, it is possible to find ourself calling this
216        // without a successful call to prepareScene. This test makes sure that unlock() will
217        // not throw IllegalMonitorStateException.
218        if (lock.isHeldByCurrentThread()) {
219            tearDown();
220            lock.unlock();
221        }
222    }
223
224    /**
225     * Sets up the session for rendering.
226     * <p/>
227     * The counterpart is {@link #tearDown()}.
228     */
229    private void setUp() {
230        // make sure the Resources object references the context (and other objects) for this
231        // scene
232        mContext.initResources();
233        sCurrentContext = mContext;
234
235        // create an InputMethodManager
236        InputMethodManager.getInstance();
237
238        LayoutLog currentLog = mParams.getLog();
239        Bridge.setLog(currentLog);
240        mContext.getRenderResources().setFrameworkResourceIdProvider(this);
241        mContext.getRenderResources().setLogger(currentLog);
242    }
243
244    /**
245     * Tear down the session after rendering.
246     * <p/>
247     * The counterpart is {@link #setUp()}.
248     */
249    private void tearDown() {
250        // The context may be null, if there was an error during init().
251        if (mContext != null) {
252            // Make sure to remove static references, otherwise we could not unload the lib
253            mContext.disposeResources();
254        }
255
256        if (sCurrentContext != null) {
257            // quit HandlerThread created during this session.
258            HandlerThread_Delegate.cleanUp(sCurrentContext);
259        }
260
261        // clear the stored ViewConfiguration since the map is per density and not per context.
262        ViewConfiguration_Accessor.clearConfigurations();
263
264        // remove the InputMethodManager
265        InputMethodManager_Accessor.resetInstance();
266
267        sCurrentContext = null;
268
269        Bridge.setLog(null);
270        if (mContext != null) {
271            mContext.getRenderResources().setFrameworkResourceIdProvider(null);
272            mContext.getRenderResources().setLogger(null);
273        }
274
275    }
276
277    public static BridgeContext getCurrentContext() {
278        return sCurrentContext;
279    }
280
281    protected T getParams() {
282        return mParams;
283    }
284
285    protected BridgeContext getContext() {
286        return mContext;
287    }
288
289    /**
290     * Returns the log associated with the session.
291     * @return the log or null if there are none.
292     */
293    public LayoutLog getLog() {
294        if (mParams != null) {
295            return mParams.getLog();
296        }
297
298        return null;
299    }
300
301    /**
302     * Checks that the lock is owned by the current thread and that the current context is the one
303     * from this scene.
304     *
305     * @throws IllegalStateException if the current context is different than the one owned by
306     *      the scene, or if {@link #acquire(long)} was not called.
307     */
308    protected void checkLock() {
309        ReentrantLock lock = Bridge.getLock();
310        if (!lock.isHeldByCurrentThread()) {
311            throw new IllegalStateException("scene must be acquired first. see #acquire(long)");
312        }
313        if (sCurrentContext != mContext) {
314            throw new IllegalStateException("Thread acquired a scene but is rendering a different one");
315        }
316    }
317
318    private Configuration getConfiguration() {
319        Configuration config = new Configuration();
320
321        HardwareConfig hardwareConfig = mParams.getHardwareConfig();
322
323        ScreenSize screenSize = hardwareConfig.getScreenSize();
324        if (screenSize != null) {
325            switch (screenSize) {
326                case SMALL:
327                    config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_SMALL;
328                    break;
329                case NORMAL:
330                    config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_NORMAL;
331                    break;
332                case LARGE:
333                    config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_LARGE;
334                    break;
335                case XLARGE:
336                    config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_XLARGE;
337                    break;
338            }
339        }
340
341        Density density = hardwareConfig.getDensity();
342        if (density == null) {
343            density = Density.MEDIUM;
344        }
345
346        config.screenWidthDp = hardwareConfig.getScreenWidth() / density.getDpiValue();
347        config.screenHeightDp = hardwareConfig.getScreenHeight() / density.getDpiValue();
348        if (config.screenHeightDp < config.screenWidthDp) {
349            //noinspection SuspiciousNameCombination
350            config.smallestScreenWidthDp = config.screenHeightDp;
351        } else {
352            config.smallestScreenWidthDp = config.screenWidthDp;
353        }
354        config.densityDpi = density.getDpiValue();
355
356        // never run in compat mode:
357        config.compatScreenWidthDp = config.screenWidthDp;
358        config.compatScreenHeightDp = config.screenHeightDp;
359
360        ScreenOrientation orientation = hardwareConfig.getOrientation();
361        if (orientation != null) {
362            switch (orientation) {
363            case PORTRAIT:
364                config.orientation = Configuration.ORIENTATION_PORTRAIT;
365                break;
366            case LANDSCAPE:
367                config.orientation = Configuration.ORIENTATION_LANDSCAPE;
368                break;
369            case SQUARE:
370                //noinspection deprecation
371                config.orientation = Configuration.ORIENTATION_SQUARE;
372                break;
373            }
374        } else {
375            config.orientation = Configuration.ORIENTATION_UNDEFINED;
376        }
377
378        // TODO: fill in more config info.
379
380        return config;
381    }
382
383
384    // --- FrameworkResourceIdProvider methods
385
386    @Override
387    public Integer getId(ResourceType resType, String resName) {
388        return Bridge.getResourceId(resType, resName);
389    }
390}
391