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