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