• 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.HardwareConfig;
24 import com.android.ide.common.rendering.api.LayoutLog;
25 import com.android.ide.common.rendering.api.RenderParams;
26 import com.android.ide.common.rendering.api.RenderResources;
27 import com.android.ide.common.rendering.api.RenderResources.FrameworkResourceIdProvider;
28 import com.android.ide.common.rendering.api.Result;
29 import com.android.layoutlib.bridge.Bridge;
30 import com.android.layoutlib.bridge.android.BridgeContext;
31 import com.android.resources.Density;
32 import com.android.resources.ResourceType;
33 import com.android.resources.ScreenOrientation;
34 import com.android.resources.ScreenSize;
35 
36 import android.content.res.Configuration;
37 import android.os.HandlerThread_Delegate;
38 import android.os.Looper;
39 import android.util.DisplayMetrics;
40 import android.view.ViewConfiguration_Accessor;
41 import android.view.inputmethod.InputMethodManager;
42 import android.view.inputmethod.InputMethodManager_Accessor;
43 
44 import java.util.concurrent.TimeUnit;
45 import 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  */
59 public 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      */
RenderAction(T params)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      */
init(long timeout)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      */
acquire(long timeout)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      */
acquireLock(long timeout)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      */
release()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      */
setUp()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      */
tearDown()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     }
277 
getCurrentContext()278     public static BridgeContext getCurrentContext() {
279         return sCurrentContext;
280     }
281 
getParams()282     protected T getParams() {
283         return mParams;
284     }
285 
getContext()286     protected BridgeContext getContext() {
287         return mContext;
288     }
289 
290     /**
291      * Returns the log associated with the session.
292      * @return the log or null if there are none.
293      */
getLog()294     public LayoutLog getLog() {
295         if (mParams != null) {
296             return mParams.getLog();
297         }
298 
299         return null;
300     }
301 
302     /**
303      * Checks that the lock is owned by the current thread and that the current context is the one
304      * from this scene.
305      *
306      * @throws IllegalStateException if the current context is different than the one owned by
307      *      the scene, or if {@link #acquire(long)} was not called.
308      */
checkLock()309     protected void checkLock() {
310         ReentrantLock lock = Bridge.getLock();
311         if (lock.isHeldByCurrentThread() == false) {
312             throw new IllegalStateException("scene must be acquired first. see #acquire(long)");
313         }
314         if (sCurrentContext != mContext) {
315             throw new IllegalStateException("Thread acquired a scene but is rendering a different one");
316         }
317     }
318 
getConfiguration()319     private Configuration getConfiguration() {
320         Configuration config = new Configuration();
321 
322         HardwareConfig hardwareConfig = mParams.getHardwareConfig();
323 
324         ScreenSize screenSize = hardwareConfig.getScreenSize();
325         if (screenSize != null) {
326             switch (screenSize) {
327                 case SMALL:
328                     config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_SMALL;
329                     break;
330                 case NORMAL:
331                     config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_NORMAL;
332                     break;
333                 case LARGE:
334                     config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_LARGE;
335                     break;
336                 case XLARGE:
337                     config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_XLARGE;
338                     break;
339             }
340         }
341 
342         Density density = hardwareConfig.getDensity();
343         if (density == null) {
344             density = Density.MEDIUM;
345         }
346 
347         config.screenWidthDp = hardwareConfig.getScreenWidth() / density.getDpiValue();
348         config.screenHeightDp = hardwareConfig.getScreenHeight() / density.getDpiValue();
349         if (config.screenHeightDp < config.screenWidthDp) {
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                 config.orientation = Configuration.ORIENTATION_SQUARE;
371                 break;
372             }
373         } else {
374             config.orientation = Configuration.ORIENTATION_UNDEFINED;
375         }
376 
377         // TODO: fill in more config info.
378 
379         return config;
380     }
381 
382 
383     // --- FrameworkResourceIdProvider methods
384 
385     @Override
getId(ResourceType resType, String resName)386     public Integer getId(ResourceType resType, String resName) {
387         return Bridge.getResourceId(resType, resName);
388     }
389 }
390