• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.layoutlib.bridge.impl;
18 
19 import static com.android.ide.common.rendering.api.Result.Status.ERROR_LOCK_INTERRUPTED;
20 import static com.android.ide.common.rendering.api.Result.Status.ERROR_TIMEOUT;
21 import static com.android.ide.common.rendering.api.Result.Status.SUCCESS;
22 
23 import com.android.ide.common.rendering.api.LayoutLog;
24 import com.android.ide.common.rendering.api.RenderParams;
25 import com.android.ide.common.rendering.api.RenderResources;
26 import com.android.ide.common.rendering.api.Result;
27 import com.android.ide.common.rendering.api.RenderResources.FrameworkResourceIdProvider;
28 import com.android.layoutlib.bridge.Bridge;
29 import com.android.layoutlib.bridge.android.BridgeContext;
30 import com.android.resources.ResourceType;
31 
32 import android.os.HandlerThread_Delegate;
33 import android.util.DisplayMetrics;
34 
35 import java.util.concurrent.TimeUnit;
36 import java.util.concurrent.locks.ReentrantLock;
37 
38 /**
39  * Base class for rendering action.
40  *
41  * It provides life-cycle methods to init and stop the rendering.
42  * The most important methods are:
43  * {@link #init(long)} and {@link #acquire(long)} to start a rendering and {@link #release()}
44  * after the rendering.
45  *
46  *
47  * @param <T> the {@link RenderParams} implementation
48  *
49  */
50 public abstract class RenderAction<T extends RenderParams> extends FrameworkResourceIdProvider {
51 
52     /**
53      * The current context being rendered. This is set through {@link #acquire(long)} and
54      * {@link #init(long)}, and unset in {@link #release()}.
55      */
56     private static BridgeContext sCurrentContext = null;
57 
58     private final T mParams;
59 
60     private BridgeContext mContext;
61 
62     /**
63      * Creates a renderAction.
64      * <p>
65      * This <b>must</b> be followed by a call to {@link RenderAction#init()}, which act as a
66      * call to {@link RenderAction#acquire(long)}
67      *
68      * @param params the RenderParams. This must be a copy that the action can keep
69      *
70      */
RenderAction(T params)71     protected RenderAction(T params) {
72         mParams = params;
73     }
74 
75     /**
76      * Initializes and acquires the scene, creating various Android objects such as context,
77      * inflater, and parser.
78      *
79      * @param timeout the time to wait if another rendering is happening.
80      *
81      * @return whether the scene was prepared
82      *
83      * @see #acquire(long)
84      * @see #release()
85      */
init(long timeout)86     public Result init(long timeout) {
87         // acquire the lock. if the result is null, lock was just acquired, otherwise, return
88         // the result.
89         Result result = acquireLock(timeout);
90         if (result != null) {
91             return result;
92         }
93 
94         // setup the display Metrics.
95         DisplayMetrics metrics = new DisplayMetrics();
96         metrics.densityDpi = mParams.getDensity().getDpiValue();
97         metrics.density = metrics.densityDpi / (float) DisplayMetrics.DENSITY_DEFAULT;
98         metrics.scaledDensity = metrics.density;
99         metrics.widthPixels = mParams.getScreenWidth();
100         metrics.heightPixels = mParams.getScreenHeight();
101         metrics.xdpi = mParams.getXdpi();
102         metrics.ydpi = mParams.getYdpi();
103 
104         RenderResources resources = mParams.getResources();
105 
106         // build the context
107         mContext = new BridgeContext(mParams.getProjectKey(), metrics, resources,
108                 mParams.getProjectCallback(), mParams.getTargetSdkVersion());
109 
110         setUp();
111 
112         return SUCCESS.createResult();
113     }
114 
115     /**
116      * Prepares the scene for action.
117      * <p>
118      * This call is blocking if another rendering/inflating is currently happening, and will return
119      * whether the preparation worked.
120      *
121      * The preparation can fail if another rendering took too long and the timeout was elapsed.
122      *
123      * More than one call to this from the same thread will have no effect and will return
124      * {@link Result#SUCCESS}.
125      *
126      * After scene actions have taken place, only one call to {@link #release()} must be
127      * done.
128      *
129      * @param timeout the time to wait if another rendering is happening.
130      *
131      * @return whether the scene was prepared
132      *
133      * @see #release()
134      *
135      * @throws IllegalStateException if {@link #init(long)} was never called.
136      */
acquire(long timeout)137     public Result acquire(long timeout) {
138         if (mContext == null) {
139             throw new IllegalStateException("After scene creation, #init() must be called");
140         }
141 
142         // acquire the lock. if the result is null, lock was just acquired, otherwise, return
143         // the result.
144         Result result = acquireLock(timeout);
145         if (result != null) {
146             return result;
147         }
148 
149         setUp();
150 
151         return SUCCESS.createResult();
152     }
153 
154     /**
155      * Acquire the lock so that the scene can be acted upon.
156      * <p>
157      * This returns null if the lock was just acquired, otherwise it returns
158      * {@link Result#SUCCESS} if the lock already belonged to that thread, or another
159      * instance (see {@link Result#getStatus()}) if an error occurred.
160      *
161      * @param timeout the time to wait if another rendering is happening.
162      * @return null if the lock was just acquire or another result depending on the state.
163      *
164      * @throws IllegalStateException if the current context is different than the one owned by
165      *      the scene.
166      */
acquireLock(long timeout)167     private Result acquireLock(long timeout) {
168         ReentrantLock lock = Bridge.getLock();
169         if (lock.isHeldByCurrentThread() == false) {
170             try {
171                 boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS);
172 
173                 if (acquired == false) {
174                     return ERROR_TIMEOUT.createResult();
175                 }
176             } catch (InterruptedException e) {
177                 return ERROR_LOCK_INTERRUPTED.createResult();
178             }
179         } else {
180             // This thread holds the lock already. Checks that this wasn't for a different context.
181             // If this is called by init, mContext will be null and so should sCurrentContext
182             // anyway
183             if (mContext != sCurrentContext) {
184                 throw new IllegalStateException("Acquiring different scenes from same thread without releases");
185             }
186             return SUCCESS.createResult();
187         }
188 
189         return null;
190     }
191 
192     /**
193      * Cleans up the scene after an action.
194      */
release()195     public void release() {
196         ReentrantLock lock = Bridge.getLock();
197 
198         // with the use of finally blocks, it is possible to find ourself calling this
199         // without a successful call to prepareScene. This test makes sure that unlock() will
200         // not throw IllegalMonitorStateException.
201         if (lock.isHeldByCurrentThread()) {
202             tearDown();
203             lock.unlock();
204         }
205     }
206 
207     /**
208      * Sets up the session for rendering.
209      * <p/>
210      * The counterpart is {@link #tearDown()}.
211      */
setUp()212     private void setUp() {
213         // make sure the Resources object references the context (and other objects) for this
214         // scene
215         mContext.initResources();
216         sCurrentContext = mContext;
217 
218         LayoutLog currentLog = mParams.getLog();
219         Bridge.setLog(currentLog);
220         mContext.getRenderResources().setFrameworkResourceIdProvider(this);
221         mContext.getRenderResources().setLogger(currentLog);
222     }
223 
224     /**
225      * Tear down the session after rendering.
226      * <p/>
227      * The counterpart is {@link #setUp()}.
228      */
tearDown()229     private void tearDown() {
230         // Make sure to remove static references, otherwise we could not unload the lib
231         mContext.disposeResources();
232 
233         // quit HandlerThread created during this session.
234         HandlerThread_Delegate.cleanUp(sCurrentContext);
235 
236         sCurrentContext = null;
237 
238         Bridge.setLog(null);
239         mContext.getRenderResources().setFrameworkResourceIdProvider(null);
240         mContext.getRenderResources().setLogger(null);
241     }
242 
getCurrentContext()243     public static BridgeContext getCurrentContext() {
244         return sCurrentContext;
245     }
246 
getParams()247     protected T getParams() {
248         return mParams;
249     }
250 
getContext()251     protected BridgeContext getContext() {
252         return mContext;
253     }
254 
255     /**
256      * Returns the log associated with the session.
257      * @return the log or null if there are none.
258      */
getLog()259     public LayoutLog getLog() {
260         if (mParams != null) {
261             return mParams.getLog();
262         }
263 
264         return null;
265     }
266 
267     /**
268      * Checks that the lock is owned by the current thread and that the current context is the one
269      * from this scene.
270      *
271      * @throws IllegalStateException if the current context is different than the one owned by
272      *      the scene, or if {@link #acquire(long)} was not called.
273      */
checkLock()274     protected void checkLock() {
275         ReentrantLock lock = Bridge.getLock();
276         if (lock.isHeldByCurrentThread() == false) {
277             throw new IllegalStateException("scene must be acquired first. see #acquire(long)");
278         }
279         if (sCurrentContext != mContext) {
280             throw new IllegalStateException("Thread acquired a scene but is rendering a different one");
281         }
282     }
283 
284     // --- FrameworkResourceIdProvider methods
285 
286     @Override
getId(ResourceType resType, String resName)287     public Integer getId(ResourceType resType, String resName) {
288         return Bridge.getResourceId(resType, resName);
289     }
290 }
291